-
Notifications
You must be signed in to change notification settings - Fork 7
feat!: introduces multisignature abstractions in utils #218
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
e54cab1
80f8517
439e267
aa934dc
6c70c01
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,47 @@ | ||||||||||||||||||||||||
| use algokit_transact::{Address, MultisigSubsignature}; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| #[derive(Debug, thiserror::Error)] | ||||||||||||||||||||||||
| pub enum MultisigError { | ||||||||||||||||||||||||
| #[error("Invalid multisig account: {0}")] | ||||||||||||||||||||||||
| InvalidMultisigSignature(#[from] algokit_transact::AlgoKitTransactError), | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| #[derive(Clone)] | ||||||||||||||||||||||||
| pub struct MultisigAccount { | ||||||||||||||||||||||||
| pub version: u8, | ||||||||||||||||||||||||
| pub threshold: u8, | ||||||||||||||||||||||||
| pub participants: Vec<Address>, | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| impl From<algokit_transact::MultisigSignature> for MultisigAccount { | ||||||||||||||||||||||||
| fn from(multisig: algokit_transact::MultisigSignature) -> Self { | ||||||||||||||||||||||||
| Self { | ||||||||||||||||||||||||
| version: multisig.version, | ||||||||||||||||||||||||
| threshold: multisig.threshold, | ||||||||||||||||||||||||
| participants: multisig.participants(), | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| impl TryFrom<MultisigAccount> for algokit_transact::MultisigSignature { | ||||||||||||||||||||||||
| type Error = MultisigError; | ||||||||||||||||||||||||
| fn try_from(account: MultisigAccount) -> Result<Self, Self::Error> { | ||||||||||||||||||||||||
| Ok(algokit_transact::MultisigSignature::from_participants( | ||||||||||||||||||||||||
| account.version, | ||||||||||||||||||||||||
| account.threshold, | ||||||||||||||||||||||||
| account.participants, | ||||||||||||||||||||||||
| )?) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| impl TryFrom<MultisigAccount> for Address { | ||||||||||||||||||||||||
| type Error = MultisigError; | ||||||||||||||||||||||||
| fn try_from(account: MultisigAccount) -> Result<Self, Self::Error> { | ||||||||||||||||||||||||
| let msig_signature: algokit_transact::MultisigSignature = account.try_into()?; | ||||||||||||||||||||||||
| Ok(msig_signature.into()) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
||||||||||||||||||||||||
| /// Represents a multisignature with optional subsignatures for each participant. | |
| /// | |
| /// This struct is distinct from `algokit_transact::MultisigSignature` in that it | |
| /// focuses on the subsignature details, allowing for partial or incomplete | |
| /// multisignatures. It is used when working with multisignature accounts where | |
| /// some participants may not have signed yet. | |
| /// | |
| /// Use this struct when you need to manage or inspect the state of individual | |
| /// subsignatures in a multisignature account. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this needed or can we leverage the existing algokit_transact MultisigSignature?
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,4 +1,5 @@ | ||||||||||||||||||||||||||
| use algokit_transact::{Address, SignedTransaction, Transaction}; | ||||||||||||||||||||||||||
| use crate::multisig::MultisigAccount; | ||||||||||||||||||||||||||
| use algokit_transact::{ALGORAND_SIGNATURE_BYTE_LENGTH, Address, SignedTransaction, Transaction}; | ||||||||||||||||||||||||||
| use async_trait::async_trait; | ||||||||||||||||||||||||||
| use derive_more::Debug; | ||||||||||||||||||||||||||
| use std::sync::Arc; | ||||||||||||||||||||||||||
|
|
@@ -15,8 +16,8 @@ pub trait TransactionSigner: Send + Sync { | |||||||||||||||||||||||||
| &self, | ||||||||||||||||||||||||||
| transaction: &Transaction, | ||||||||||||||||||||||||||
| ) -> Result<SignedTransaction, String> { | ||||||||||||||||||||||||||
| let result = self.sign_transactions(&[transaction.clone()], &[0]).await?; | ||||||||||||||||||||||||||
| Ok(result[0].clone()) | ||||||||||||||||||||||||||
| let mut result = self.sign_transactions(&[transaction.clone()], &[0]).await?; | ||||||||||||||||||||||||||
| Ok(result.remove(0)) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
@@ -25,10 +26,10 @@ pub trait TransactionSignerGetter { | |||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| #[derive(Clone)] | ||||||||||||||||||||||||||
| pub struct EmptySigner {} | ||||||||||||||||||||||||||
| pub struct EmptyKeyPairSigner {} | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| #[async_trait] | ||||||||||||||||||||||||||
| impl TransactionSigner for EmptySigner { | ||||||||||||||||||||||||||
| impl TransactionSigner for EmptyKeyPairSigner { | ||||||||||||||||||||||||||
| async fn sign_transactions( | ||||||||||||||||||||||||||
| &self, | ||||||||||||||||||||||||||
| txns: &[Transaction], | ||||||||||||||||||||||||||
|
|
@@ -40,7 +41,7 @@ impl TransactionSigner for EmptySigner { | |||||||||||||||||||||||||
| if idx < txns.len() { | ||||||||||||||||||||||||||
| Ok(SignedTransaction { | ||||||||||||||||||||||||||
| transaction: txns[idx].clone(), | ||||||||||||||||||||||||||
| signature: Some([0; 64]), | ||||||||||||||||||||||||||
| signature: Some([0; ALGORAND_SIGNATURE_BYTE_LENGTH]), | ||||||||||||||||||||||||||
| auth_address: None, | ||||||||||||||||||||||||||
| multisignature: None, | ||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||
|
|
@@ -52,7 +53,47 @@ impl TransactionSigner for EmptySigner { | |||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
| /// A signer implementation for multisignature accounts that produces placeholder signatures. | |
| /// | |
| /// # Purpose | |
| /// This struct is intended for use in testing or scenarios where real signatures are not required. | |
| /// It creates a multisignature with empty subsignatures (`[0; ALGORAND_SIGNATURE_BYTE_LENGTH]`). | |
| /// | |
| /// # Behavior | |
| /// When signing transactions, it populates the multisignature with placeholder subsignatures | |
| /// and does not produce a valid cryptographic signature. This makes it unsuitable for | |
| /// production use but useful for testing or as a placeholder. |
Copilot
AI
Jul 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting all subsignatures to zero bytes creates an invalid multisig signature. This could be misleading for testing purposes. Consider adding a comment explaining that this creates dummy signatures for testing, or provide a way to create valid empty signatures.
| multisig | |
| .subsignatures | |
| .iter_mut() | |
| .for_each(|subsig| subsig.signature = Some([0; ALGORAND_SIGNATURE_BYTE_LENGTH])); | |
| // Set dummy signatures for testing purposes. These are not valid signatures | |
| // and should only be used in non-production scenarios. | |
| multisig | |
| .subsignatures | |
| .iter_mut() | |
| .for_each(|subsig| subsig.signature = Some(Self::generate_dummy_signature())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not completely clear how we intend this to be used.
The
MultisigAccountfrom utils-ts for example requires either accounts with private keys or signer accounts on construction, giving the account the required info to be able to also sign the transaction. https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/account.ts#L20