diff -U2 -r /var/lib/copr-rpmbuild/results/i2pd-git/upstream-unpacked/Source0/i2pd-openssl/libi2pd/Destination.cpp /var/lib/copr-rpmbuild/results/i2pd-git/srpm-unpacked/i2pd-openssl.tar.gz-extract/i2pd-openssl/libi2pd/Destination.cpp --- /var/lib/copr-rpmbuild/results/i2pd-git/upstream-unpacked/Source0/i2pd-openssl/libi2pd/Destination.cpp 2026-06-22 20:39:34.000000000 +0000 +++ /var/lib/copr-rpmbuild/results/i2pd-git/srpm-unpacked/i2pd-openssl.tar.gz-extract/i2pd-openssl/libi2pd/Destination.cpp 2026-06-22 18:09:59.000000000 +0000 @@ -1170,19 +1170,15 @@ void ClientDestination::UpdateOfflineSignature (const i2p::data::PrivateKeys& keys) { - // Adopt a newer offline transient of the same identity in place, on the - // destination's service so it stays single-threaded with LeaseSet and stream - // signing. Only the transient is refreshed, the identity is unchanged. - if (!keys.IsOfflineSignature ()) return; + // Adopt a newer offline transient of the same identity in place: swap keys and + // republish the LeaseSet, keeping tunnels and streams (unlike SetPrivateKeys). + if (!keys.IsOfflineSignature () || !m_Keys.IsOfflineSignature ()) return; + if (keys.GetPublic ()->GetIdentHash () != GetIdentHash ()) return; + const uint32_t expires = bufbe32toh (keys.GetOfflineSignature ().data ()); + if (expires <= bufbe32toh (m_Keys.GetOfflineSignature ().data ())) return; // only move forward + LogPrint (eLogInfo, "Destination: Refreshing offline signature for ", + GetIdentHash ().ToBase32 (), ", transient expires ", expires); boost::asio::post (GetService (), [s = GetSharedFromThis (), keys]() { - if (!s->m_Keys.IsOfflineSignature ()) return; - if (keys.GetPublic ()->GetIdentHash () != s->GetIdentHash ()) return; - const auto& next = keys.GetOfflineSignature (); - const auto& cur = s->m_Keys.GetOfflineSignature (); - if (next == cur) return; // same transient - if (bufbe32toh (next.data ()) < bufbe32toh (cur.data ())) return; // do not shorten validity - LogPrint (eLogInfo, "Destination: Refreshing offline signature for ", - s->GetIdentHash ().ToBase32 (), ", transient expires ", bufbe32toh (next.data ())); - s->m_Keys.UpdateOfflineSignature (keys); + s->m_Keys = keys; s->UpdateLeaseSet (); }); diff -U2 -r /var/lib/copr-rpmbuild/results/i2pd-git/upstream-unpacked/Source0/i2pd-openssl/libi2pd/Identity.cpp /var/lib/copr-rpmbuild/results/i2pd-git/srpm-unpacked/i2pd-openssl.tar.gz-extract/i2pd-openssl/libi2pd/Identity.cpp --- /var/lib/copr-rpmbuild/results/i2pd-git/upstream-unpacked/Source0/i2pd-openssl/libi2pd/Identity.cpp 2026-06-22 20:39:34.000000000 +0000 +++ /var/lib/copr-rpmbuild/results/i2pd-git/srpm-unpacked/i2pd-openssl.tar.gz-extract/i2pd-openssl/libi2pd/Identity.cpp 2026-06-22 18:09:59.000000000 +0000 @@ -644,15 +644,4 @@ } - void PrivateKeys::UpdateOfflineSignature (const PrivateKeys& other) - { - // same identity (m_Public): refresh only the transient material and the signer - m_SigningPrivateKey = other.m_SigningPrivateKey; - m_OfflineSignature = other.m_OfflineSignature; - m_TransientSignatureLen = other.m_TransientSignatureLen; - m_TransientSigningPrivateKeyLen = other.m_TransientSigningPrivateKeyLen; - m_Signer = nullptr; - CreateSigner (); - } - void PrivateKeys::CreateSigner () const { diff -U2 -r /var/lib/copr-rpmbuild/results/i2pd-git/upstream-unpacked/Source0/i2pd-openssl/libi2pd/Identity.h /var/lib/copr-rpmbuild/results/i2pd-git/srpm-unpacked/i2pd-openssl.tar.gz-extract/i2pd-openssl/libi2pd/Identity.h --- /var/lib/copr-rpmbuild/results/i2pd-git/upstream-unpacked/Source0/i2pd-openssl/libi2pd/Identity.h 2026-06-22 20:39:34.000000000 +0000 +++ /var/lib/copr-rpmbuild/results/i2pd-git/srpm-unpacked/i2pd-openssl.tar.gz-extract/i2pd-openssl/libi2pd/Identity.h 2026-06-22 18:09:59.000000000 +0000 @@ -192,5 +192,4 @@ PrivateKeys CreateOfflineKeys (SigningKeyType type, uint32_t expires) const; const std::vector& GetOfflineSignature () const { return m_OfflineSignature; }; - void UpdateOfflineSignature (const PrivateKeys& other); // refresh transient material, keep identity private: