Skip to content

Commit

Permalink
Merge pull request #3770 from Rohde-Schwarz/feature/pubkey_create_ano…
Browse files Browse the repository at this point in the history
…ther

Feature: `AsymmetricKey::generate_another()`
  • Loading branch information
reneme authored Oct 25, 2023
2 parents 64e0154 + a878ffa commit 11b5d80
Show file tree
Hide file tree
Showing 46 changed files with 196 additions and 33 deletions.
14 changes: 14 additions & 0 deletions src/lib/prov/pkcs11/p11_ecdh.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ class BOTAN_PUBLIC_API(2, 0) PKCS11_ECDH_PublicKey : public PKCS11_EC_PublicKey

inline std::string algo_name() const override { return "ECDH"; }

/**
* @throws Not_Implemented
*/
std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator&) const final {
throw Not_Implemented("Cannot generate a new PKCS#11 ECDH keypair from this public key");
}

bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::KeyAgreement); }

/// @return the exported ECDH public key
Expand Down Expand Up @@ -97,6 +104,13 @@ class BOTAN_PUBLIC_API(2, 0) PKCS11_ECDH_PrivateKey final : public virtual PKCS1

secure_vector<uint8_t> private_key_bits() const override;

/**
* @throws Not_Implemented
*/
std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator&) const override {
throw Not_Implemented("Cannot generate a new PKCS#11 ECDH keypair from this private key");
}

bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::KeyAgreement); }

std::unique_ptr<PK_Ops::Key_Agreement> create_key_agreement_op(RandomNumberGenerator& rng,
Expand Down
14 changes: 14 additions & 0 deletions src/lib/prov/pkcs11/p11_ecdsa.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ class BOTAN_PUBLIC_API(2, 0) PKCS11_ECDSA_PublicKey final : public PKCS11_EC_Pub
/// @return the exported ECDSA public key
ECDSA_PublicKey export_key() const;

/**
* @throws Not_Implemented
*/
std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator&) const final {
throw Not_Implemented("Cannot generate a new PKCS#11 ECDSA keypair from this public key");
}

std::unique_ptr<PK_Ops::Verification> create_verification_op(std::string_view params,
std::string_view provider) const override;
};
Expand Down Expand Up @@ -89,6 +96,13 @@ class BOTAN_PUBLIC_API(2, 0) PKCS11_ECDSA_PrivateKey final : public PKCS11_EC_Pr

inline std::string algo_name() const override { return "ECDSA"; }

/**
* @throws Not_Implemented
*/
std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator&) const override {
throw Not_Implemented("Cannot generate a new PKCS#11 ECDSA keypair from this private key");
}

bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::Signature); }

size_t message_parts() const override { return 2; }
Expand Down
7 changes: 7 additions & 0 deletions src/lib/prov/pkcs11/p11_rsa.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ class BOTAN_PUBLIC_API(2, 0) PKCS11_RSA_PublicKey : public Object,
*/
PKCS11_RSA_PublicKey(Session& session, const RSA_PublicKeyImportProperties& pubkey_props);

/**
* @throws Not_Implemented
*/
std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator&) const final {
throw Not_Implemented("Cannot generate a new PKCS#11 RSA keypair from this public key");
}

std::unique_ptr<PK_Ops::Encryption> create_encryption_op(RandomNumberGenerator& rng,
std::string_view params,
std::string_view provider) const override;
Expand Down
4 changes: 4 additions & 0 deletions src/lib/prov/tpm/tpm.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ class BOTAN_PUBLIC_API(2, 0) TPM_PrivateKey final : public Private_Key {

std::string algo_name() const override { return "RSA"; } // ???

std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator&) const override {
throw Not_Implemented("Cannot generate a new TPM-based keypair from this asymmetric key");
}

bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::Signature); }

std::unique_ptr<PK_Ops::Signature> create_signature_op(RandomNumberGenerator& rng,
Expand Down
4 changes: 4 additions & 0 deletions src/lib/pubkey/curve25519/curve25519.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ std::vector<uint8_t> Curve25519_PublicKey::public_key_bits() const {
return m_public;
}

std::unique_ptr<Private_Key> Curve25519_PublicKey::generate_another(RandomNumberGenerator& rng) const {
return std::make_unique<Curve25519_PrivateKey>(rng);
};

Curve25519_PrivateKey::Curve25519_PrivateKey(const secure_vector<uint8_t>& secret_key) {
if(secret_key.size() != 32) {
throw Decoding_Error("Invalid size for Curve25519 private key");
Expand Down
2 changes: 2 additions & 0 deletions src/lib/pubkey/curve25519/curve25519.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class BOTAN_PUBLIC_API(2, 0) Curve25519_PublicKey : public virtual Public_Key {

bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::KeyAgreement); }

std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;

/**
* Create a Curve25519 Public Key.
* @param alg_id the X.509 algorithm identifier
Expand Down
4 changes: 4 additions & 0 deletions src/lib/pubkey/dh/dh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ bool DH_PublicKey::check_key(RandomNumberGenerator& rng, bool strong) const {
return m_public_key->check_key(rng, strong);
}

std::unique_ptr<Private_Key> DH_PublicKey::generate_another(RandomNumberGenerator& rng) const {
return std::make_unique<DH_PrivateKey>(rng, group());
}

DH_PrivateKey::DH_PrivateKey(RandomNumberGenerator& rng, const DL_Group& group) {
m_private_key = std::make_shared<DL_PrivateKey>(group, rng);
m_public_key = m_private_key->public_key();
Expand Down
2 changes: 2 additions & 0 deletions src/lib/pubkey/dh/dh.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ class BOTAN_PUBLIC_API(2, 0) DH_PublicKey : public virtual Public_Key {

bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::KeyAgreement); }

std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;

const DL_Group& group() const;

private:
Expand Down
4 changes: 4 additions & 0 deletions src/lib/pubkey/dilithium/dilithium_common/dilithium.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,10 @@ bool Dilithium_PublicKey::check_key(RandomNumberGenerator&, bool) const {
return true; // ???
}

std::unique_ptr<Private_Key> Dilithium_PublicKey::generate_another(RandomNumberGenerator& rng) const {
return std::make_unique<Dilithium_PrivateKey>(rng, m_public->mode().mode());
}

std::unique_ptr<PK_Ops::Verification> Dilithium_PublicKey::create_verification_op(std::string_view params,
std::string_view provider) const {
BOTAN_ARG_CHECK(params.empty() || params == "Pure", "Unexpected parameters for verifying with Dilithium");
Expand Down
2 changes: 2 additions & 0 deletions src/lib/pubkey/dilithium/dilithium_common/dilithium.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ class BOTAN_PUBLIC_API(3, 0) Dilithium_PublicKey : public virtual Public_Key {

bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::Signature); }

std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;

Dilithium_PublicKey(const AlgorithmIdentifier& alg_id, std::span<const uint8_t> pk);

Dilithium_PublicKey(std::span<const uint8_t> pk, DilithiumMode mode);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ class DilithiumModeConstants {

OID oid() const { return m_mode.object_identifier(); }

DilithiumMode mode() const { return m_mode; }

size_t private_key_bytes() const { return m_private_key_bytes; }

size_t nist_security_strength() const { return m_nist_security_strength; }
Expand Down
4 changes: 4 additions & 0 deletions src/lib/pubkey/dsa/dsa.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ bool DSA_PublicKey::check_key(RandomNumberGenerator& rng, bool strong) const {
return m_public_key->check_key(rng, strong);
}

std::unique_ptr<Private_Key> DSA_PublicKey::generate_another(RandomNumberGenerator& rng) const {
return std::make_unique<DSA_PrivateKey>(rng, m_public_key->group());
}

DSA_PublicKey::DSA_PublicKey(const AlgorithmIdentifier& alg_id, std::span<const uint8_t> key_bits) {
m_public_key = std::make_shared<DL_PublicKey>(alg_id, key_bits, DL_Group_Format::ANSI_X9_57);

Expand Down
2 changes: 2 additions & 0 deletions src/lib/pubkey/dsa/dsa.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ class BOTAN_PUBLIC_API(2, 0) DSA_PublicKey : public virtual Public_Key {

bool check_key(RandomNumberGenerator& rng, bool strong) const override;

std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;

size_t estimated_strength() const override;
size_t key_length() const override;

Expand Down
4 changes: 4 additions & 0 deletions src/lib/pubkey/ecdh/ecdh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ class ECDH_KA_Operation final : public PK_Ops::Key_Agreement_with_KDF {

} // namespace

std::unique_ptr<Private_Key> ECDH_PublicKey::generate_another(RandomNumberGenerator& rng) const {
return std::make_unique<ECDH_PrivateKey>(rng, domain());
}

std::unique_ptr<PK_Ops::Key_Agreement> ECDH_PrivateKey::create_key_agreement_op(RandomNumberGenerator& rng,
std::string_view params,
std::string_view provider) const {
Expand Down
2 changes: 2 additions & 0 deletions src/lib/pubkey/ecdh/ecdh.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ class BOTAN_PUBLIC_API(2, 0) ECDH_PublicKey : public virtual EC_PublicKey {

bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::KeyAgreement); }

std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;

protected:
ECDH_PublicKey() = default;
};
Expand Down
4 changes: 4 additions & 0 deletions src/lib/pubkey/ecdsa/ecdsa.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ ECDSA_PublicKey::ECDSA_PublicKey(
const EC_Group& group, const std::vector<uint8_t>& msg, const BigInt& r, const BigInt& s, uint8_t v) :
EC_PublicKey(group, recover_ecdsa_public_key(group, msg, r, s, v)) {}

std::unique_ptr<Private_Key> ECDSA_PublicKey::generate_another(RandomNumberGenerator& rng) const {
return std::make_unique<ECDSA_PrivateKey>(rng, domain());
}

uint8_t ECDSA_PublicKey::recovery_param(const std::vector<uint8_t>& msg, const BigInt& r, const BigInt& s) const {
for(uint8_t v = 0; v != 4; ++v) {
try {
Expand Down
2 changes: 2 additions & 0 deletions src/lib/pubkey/ecdsa/ecdsa.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ class BOTAN_PUBLIC_API(2, 0) ECDSA_PublicKey : public virtual EC_PublicKey {

bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::Signature); }

std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const override;

uint8_t recovery_param(const std::vector<uint8_t>& msg, const BigInt& r, const BigInt& s) const;

std::unique_ptr<PK_Ops::Verification> create_verification_op(std::string_view params,
Expand Down
4 changes: 4 additions & 0 deletions src/lib/pubkey/ecgdsa/ecgdsa.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ bool ECGDSA_Verification_Operation::verify(const uint8_t msg[], size_t msg_len,

} // namespace

std::unique_ptr<Private_Key> ECGDSA_PublicKey::generate_another(RandomNumberGenerator& rng) const {
return std::make_unique<ECGDSA_PrivateKey>(rng, domain());
}

std::unique_ptr<PK_Ops::Verification> ECGDSA_PublicKey::create_verification_op(std::string_view params,
std::string_view provider) const {
if(provider == "base" || provider.empty()) {
Expand Down
2 changes: 2 additions & 0 deletions src/lib/pubkey/ecgdsa/ecgdsa.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ class BOTAN_PUBLIC_API(2, 0) ECGDSA_PublicKey : public virtual EC_PublicKey {

size_t message_part_size() const override { return domain().get_order().bytes(); }

std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;

bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::Signature); }

std::unique_ptr<PK_Ops::Verification> create_verification_op(std::string_view params,
Expand Down
4 changes: 4 additions & 0 deletions src/lib/pubkey/ecies/ecies.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ class ECIES_PrivateKey final : public EC_PrivateKey,

bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::KeyAgreement); }

std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const override {
return m_key.generate_another(rng);
}

std::unique_ptr<PK_Ops::Key_Agreement> create_key_agreement_op(RandomNumberGenerator& rng,
std::string_view params,
std::string_view provider) const override;
Expand Down
4 changes: 4 additions & 0 deletions src/lib/pubkey/eckcdsa/eckcdsa.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,10 @@ bool ECKCDSA_Verification_Operation::verify(const uint8_t msg[], size_t msg_len,

} // namespace

std::unique_ptr<Private_Key> ECKCDSA_PublicKey::generate_another(RandomNumberGenerator& rng) const {
return std::make_unique<ECKCDSA_PrivateKey>(rng, domain());
}

std::unique_ptr<PK_Ops::Verification> ECKCDSA_PublicKey::create_verification_op(std::string_view params,
std::string_view provider) const {
if(provider == "base" || provider.empty()) {
Expand Down
2 changes: 2 additions & 0 deletions src/lib/pubkey/eckcdsa/eckcdsa.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class BOTAN_PUBLIC_API(2, 0) ECKCDSA_PublicKey : public virtual EC_PublicKey {

size_t message_part_size() const override { return domain().get_order().bytes(); }

std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;

bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::Signature); }

std::unique_ptr<PK_Ops::Verification> create_verification_op(std::string_view params,
Expand Down
2 changes: 2 additions & 0 deletions src/lib/pubkey/ed25519/ed25519.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ class BOTAN_PUBLIC_API(2, 2) Ed25519_PublicKey : public virtual Public_Key {

std::vector<uint8_t> public_key_bits() const override;

std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;

bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::Signature); }

const std::vector<uint8_t>& get_public_key() const { return m_public; }
Expand Down
4 changes: 4 additions & 0 deletions src/lib/pubkey/ed25519/ed25519_key.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ std::vector<uint8_t> Ed25519_PublicKey::public_key_bits() const {
return m_public;
}

std::unique_ptr<Private_Key> Ed25519_PublicKey::generate_another(RandomNumberGenerator& rng) const {
return std::make_unique<Ed25519_PrivateKey>(rng);
}

Ed25519_PrivateKey::Ed25519_PrivateKey(const secure_vector<uint8_t>& secret_key) {
if(secret_key.size() == 64) {
m_private = secret_key;
Expand Down
4 changes: 4 additions & 0 deletions src/lib/pubkey/elgamal/elgamal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ const BigInt& ElGamal_PublicKey::get_int_field(std::string_view field) const {
return m_public_key->get_int_field(algo_name(), field);
}

std::unique_ptr<Private_Key> ElGamal_PublicKey::generate_another(RandomNumberGenerator& rng) const {
return std::make_unique<ElGamal_PrivateKey>(rng, m_public_key->group());
}

bool ElGamal_PublicKey::check_key(RandomNumberGenerator& rng, bool strong) const {
return m_public_key->check_key(rng, strong);
}
Expand Down
2 changes: 2 additions & 0 deletions src/lib/pubkey/elgamal/elgamal.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ class BOTAN_PUBLIC_API(2, 0) ElGamal_PublicKey : public virtual Public_Key {

const BigInt& get_int_field(std::string_view field) const override;

std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;

std::unique_ptr<PK_Ops::Encryption> create_encryption_op(RandomNumberGenerator& rng,
std::string_view params,
std::string_view provider) const override;
Expand Down
4 changes: 4 additions & 0 deletions src/lib/pubkey/gost_3410/gost_3410.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,10 @@ bool GOST_3410_Verification_Operation::verify(const uint8_t msg[],

} // namespace

std::unique_ptr<Private_Key> GOST_3410_PublicKey::generate_another(RandomNumberGenerator& rng) const {
return std::make_unique<GOST_3410_PrivateKey>(rng, domain());
}

std::unique_ptr<PK_Ops::Verification> GOST_3410_PublicKey::create_verification_op(std::string_view params,
std::string_view provider) const {
if(provider == "base" || provider.empty()) {
Expand Down
2 changes: 2 additions & 0 deletions src/lib/pubkey/gost_3410/gost_3410.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ class BOTAN_PUBLIC_API(2, 0) GOST_3410_PublicKey : public virtual EC_PublicKey {

Signature_Format default_x509_signature_format() const override { return Signature_Format::Standard; }

std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;

bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::Signature); }

std::unique_ptr<PK_Ops::Verification> create_verification_op(std::string_view params,
Expand Down
4 changes: 4 additions & 0 deletions src/lib/pubkey/kyber/kyber_common/kyber.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1247,6 +1247,10 @@ bool Kyber_PublicKey::check_key(RandomNumberGenerator&, bool) const {
return true; // ??
}

std::unique_ptr<Private_Key> Kyber_PublicKey::generate_another(RandomNumberGenerator& rng) const {
return std::make_unique<Kyber_PrivateKey>(rng, mode());
}

Kyber_PrivateKey::Kyber_PrivateKey(RandomNumberGenerator& rng, KyberMode m) {
KyberConstants mode(m);

Expand Down
2 changes: 2 additions & 0 deletions src/lib/pubkey/kyber/kyber_common/kyber.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ class BOTAN_PUBLIC_API(3, 0) Kyber_PublicKey : public virtual Public_Key {

bool check_key(RandomNumberGenerator&, bool) const override;

std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;

bool supports_operation(PublicKeyOperation op) const override {
return (op == PublicKeyOperation::KeyEncapsulation);
}
Expand Down
2 changes: 2 additions & 0 deletions src/lib/pubkey/mce/mceliece.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ class BOTAN_PUBLIC_API(2, 0) McEliece_PublicKey : public virtual Public_Key {

bool operator!=(const McEliece_PublicKey& other) const { return !(*this == other); }

std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;

bool supports_operation(PublicKeyOperation op) const override {
return (op == PublicKeyOperation::KeyEncapsulation);
}
Expand Down
4 changes: 4 additions & 0 deletions src/lib/pubkey/mce/mceliece_key.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,10 @@ class MCE_KEM_Decryptor final : public PK_Ops::KEM_Decryption_with_KDF {

} // namespace

std::unique_ptr<Private_Key> McEliece_PublicKey::generate_another(RandomNumberGenerator& rng) const {
return std::make_unique<McEliece_PrivateKey>(rng, get_code_length(), get_t());
}

std::unique_ptr<PK_Ops::KEM_Encryption> McEliece_PublicKey::create_kem_encryption_op(std::string_view params,
std::string_view provider) const {
if(provider == "base" || provider.empty()) {
Expand Down
8 changes: 8 additions & 0 deletions src/lib/pubkey/pk_keys.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ enum class PublicKeyOperation {
KeyAgreement,
};

class Private_Key;

/**
* An interface for objects that are keys in public key algorithms
*
Expand Down Expand Up @@ -99,6 +101,12 @@ class BOTAN_PUBLIC_API(3, 0) Asymmetric_Key {
* of operation.
*/
virtual bool supports_operation(PublicKeyOperation op) const = 0;

/**
* Generate another (cryptographically independent) key pair using the
* same algorithm parameters as this key.
*/
virtual std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const = 0;
};

/*
Expand Down
4 changes: 4 additions & 0 deletions src/lib/pubkey/rsa/rsa.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ const BigInt& RSA_PublicKey::get_int_field(std::string_view field) const {
}
}

std::unique_ptr<Private_Key> RSA_PublicKey::generate_another(RandomNumberGenerator& rng) const {
return std::make_unique<RSA_PrivateKey>(rng, m_public->public_modulus_bits(), m_public->get_e().to_u32bit());
}

const BigInt& RSA_PublicKey::get_n() const {
return m_public->get_n();
}
Expand Down
Loading

0 comments on commit 11b5d80

Please sign in to comment.