Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion src/sdk/main/include/Transaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,23 @@ class Transaction
[[nodiscard]] virtual std::map<AccountId, std::map<std::shared_ptr<PublicKey>, std::vector<std::byte>>>
getSignatures() const;

/**
* This method removes all signatures from the transaction based on the public key provided.
*
* @param publicKey The public key associated with the signature to remove.
* @return The removed signatures.
* @throws IllegalStateException if transaction is not frozen or the given key didn't sign it off.
*/
std::vector<std::vector<std::byte>> removeSignature(const std::shared_ptr<PublicKey>& publicKey);

/**
* Remove all signatures from the transaction.
*
* @return The removed signatures grouped by their associated public key.
* @throws IllegalStateException if the transaction is not frozen.
*/
std::map<std::shared_ptr<PublicKey>, std::vector<std::vector<std::byte>>> removeAllSignatures();

/**
* Freeze this Transaction.
*
Expand Down Expand Up @@ -575,4 +592,4 @@ class Transaction

} // namespace Hiero

#endif // HIERO_SDK_CPP_TRANSACTION_H_
#endif // HIERO_SDK_CPP_TRANSACTION_H_
161 changes: 160 additions & 1 deletion src/sdk/main/src/Transaction.cc
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,34 @@

namespace Hiero
{

namespace
{
/**
* Helper function - Extract the raw signature bytes from a protobuf SignaturePair.
*/
std::vector<std::byte> extractSignatureBytes(const proto::SignaturePair& pair)
{
const std::string* sigStr = nullptr;
if (pair.has_ed25519())
{
sigStr = &pair.ed25519();
}
else if (pair.has_ecdsa_secp256k1())
{
sigStr = &pair.ecdsa_secp256k1();
}
else
{
throw IllegalStateException("Unknown signature type");
}

std::vector<std::byte> sigBytes(sigStr->size());
std::transform(sigStr->begin(), sigStr->end(), sigBytes.begin(), [](char c) { return static_cast<std::byte>(c); });
return sigBytes;
}
} // anonymous namespace

//-----
template<typename SdkRequestType>
struct Transaction<SdkRequestType>::TransactionImpl
Expand Down Expand Up @@ -556,6 +584,137 @@ Transaction<SdkRequestType>::getSignatures() const
return getSignaturesInternal();
}

//-----
template<typename SdkRequestType>
std::vector<std::vector<std::byte>> Transaction<SdkRequestType>::removeSignature(
const std::shared_ptr<PublicKey>& publicKey)
{
if (!isFrozen())
{
throw IllegalStateException("Removing a signature from a Transaction requires "
"the Transaction to be frozen");
}

if (!keyAlreadySigned(publicKey))
{
throw IllegalStateException("The public key has not signed this transaction");
}

std::vector<std::vector<std::byte>> removedSignatures;

// Build the prefix string, exactly as protobuf stores it (raw bytes as string)
const std::string pubKeyPrefixStr = internal::Utilities::byteVectorToString(publicKey->toBytesRaw());

// Identify external signatures to be kept in the protobuf
std::unordered_set<std::string> externalPrefixesToKeep;
for (const auto& [key, signer] : mImpl->mSignatories)
{
// If it's not the given public key and it has no signer function (!signer)
if (key->toBytesDer() != publicKey->toBytesDer() && !signer)
{
externalPrefixesToKeep.insert(internal::Utilities::byteVectorToString(key->toBytesRaw()));
}
}

// Remove the target signature from the compiled Protobuf messages
for (auto& signedTransaction : mImpl->mSignedTransactions)
{
auto* sigPairs = signedTransaction.mutable_sigmap()->mutable_sigpair();

// Iterate backwards so we can safely erase elements in-place without shifting indices
for (int i = sigPairs->size() - 1; i >= 0; --i)
{
const auto& pair = sigPairs->at(i);

if (pair.pubkeyprefix() == pubKeyPrefixStr)
{
removedSignatures.push_back(extractSignatureBytes(pair));
// Erase the signature directly from the Protobuf array
sigPairs->erase(sigPairs->begin() + i);
}
else if (externalPrefixesToKeep.find(pair.pubkeyprefix()) == externalPrefixesToKeep.end())
{
// This is a "stale" auto-generated signature.
// Erase it so it can be cleanly regenerated without duplication.
sigPairs->erase(sigPairs->begin() + i);
}
}

if (sigPairs->empty())
{
signedTransaction.clear_sigmap();
}
}

// Remove the key from the SDK's internal tracking lists
for (auto it = mImpl->mSignatories.begin(); it != mImpl->mSignatories.end(); ++it)
{
if (it->first->toBytesDer() == publicKey->toBytesDer())
{
mImpl->mPrivateKeys.erase(it->first);
mImpl->mSignatories.erase(it);
break;
}
}

// Clear and resize the raw transactions array to match the updated signed state
mImpl->mTransactions.clear();
mImpl->mTransactions.resize(mImpl->mSignedTransactions.size());

return removedSignatures;
}

//-----
template<typename SdkRequestType>
std::map<std::shared_ptr<PublicKey>, std::vector<std::vector<std::byte>>>
Transaction<SdkRequestType>::removeAllSignatures()
{
if (!isFrozen())
{
throw IllegalStateException("Removing signatures from a Transaction requires "
"the Transaction to be frozen");
}

std::map<std::shared_ptr<PublicKey>, std::vector<std::vector<std::byte>>> removedByKey;

// Create a lookup map mapping string prefixes to tracked PublicKey objects
std::unordered_map<std::string, std::shared_ptr<PublicKey>> prefixToKey;
for (const auto& [key, signer] : mImpl->mSignatories)
{
prefixToKey.emplace(internal::Utilities::byteVectorToString(key->toBytesRaw()), key);
}

// Extract signatures and clear them from the compiled protobuf messages
for (auto& signedTransaction : mImpl->mSignedTransactions)
{
auto* sigMap = signedTransaction.mutable_sigmap();
auto* sigPairs = sigMap->mutable_sigpair();

for (const auto& pair : *sigPairs)
{
auto it = prefixToKey.find(pair.pubkeyprefix());
if (it != prefixToKey.end())
{
std::vector<std::byte> sigBytes = extractSignatureBytes(pair);
removedByKey[it->second].push_back(std::move(sigBytes));
}
}

// Wipe out all signatures on this specific node's transaction
signedTransaction.clear_sigmap();
}

// Wipe all internal SDK tracking states
mImpl->mSignatories.clear();
mImpl->mPrivateKeys.clear();

// Clear and resize the raw transactions array to match the updated signed state
mImpl->mTransactions.clear();
mImpl->mTransactions.resize(mImpl->mSignedTransactions.size());

return removedByKey;
}

//-----
template<typename SdkRequestType>
SdkRequestType& Transaction<SdkRequestType>::freeze()
Expand Down Expand Up @@ -1488,4 +1647,4 @@ template class Transaction<TopicMessageSubmitTransaction>;
template class Transaction<TopicUpdateTransaction>;
template class Transaction<TransferTransaction>;

} // namespace Hiero
} // namespace Hiero
Loading
Loading