Пример #1
0
bool OTEnvelope::Seal(const OTPseudonym & theRecipient, const OTString & theInput)
{
    OTString              strNymID;
    mapOfAsymmetricKeys   theKeys;
    theRecipient.GetIdentifier(strNymID);
    theKeys.insert(std::pair<std::string, OTAsymmetricKey *>
                   (strNymID.Get(), const_cast<OTAsymmetricKey *>(&(theRecipient.GetPublicEncrKey()))));
    // -----------------------------
    return this->Seal(theKeys, theInput);
}
Пример #2
0
// Verify Contract ID first, THEN Verify Owner.
// Because we use the ID in this function, so make sure that it is verified before calling this.
bool OTAccount::VerifyOwner(OTPseudonym & theCandidate)
{
	OTIdentifier ID_CANDIDATE;
	theCandidate.GetIdentifier(ID_CANDIDATE); // ID_CANDIDATE now contains the ID of the Nym we're testing.
	
	if (m_AcctUserID == ID_CANDIDATE) // There's an op== function, but no != ... So I used == since it exists.
	{
		return true;
	}
	return false;
}
// Verify Contract ID first, THEN Verify Owner.
// Because we use the ID in this function, so make sure that it is verified before calling this.
//
bool OTAccount::VerifyOwner(const OTPseudonym & theCandidate) const
{
	OTIdentifier ID_CANDIDATE;
	theCandidate.GetIdentifier(ID_CANDIDATE); // ID_CANDIDATE now contains the ID of the Nym we're testing.

	if (m_AcctUserID == ID_CANDIDATE)
	{
		return true;
	}
	return false;
}
Пример #4
0
	FOR_EACH(mapOfNyms, m_mapNyms)
	{		
		OTPseudonym * pNym = (*it).second;
		OT_ASSERT_MSG((NULL != pNym), "NULL pseudonym pointer in OTWallet::GetNymByID.");

		OTIdentifier id_CurrentNym;
		pNym->GetIdentifier(id_CurrentNym);
		
		if (id_CurrentNym == NYM_ID)
			return pNym;
	}	
Пример #5
0
bool OTEnvelope::Seal(setOfNyms & theRecipients, const OTString & theInput)
{
    mapOfAsymmetricKeys RecipPubKeys;
    
    // Loop through theRecipients, and add the public key of each one to a set of keys.
    //
    FOR_EACH(setOfNyms, theRecipients)
    {
        OTPseudonym * pNym = *it;
		OT_ASSERT_MSG(NULL != pNym, "OTEnvelope::Seal: Assert: NULL pseudonym pointer.");
		// ------------------------------
        OTString            strNymID;
        pNym->GetIdentifier(strNymID);
        RecipPubKeys.insert(std::pair<std::string, OTAsymmetricKey *>
                            (strNymID.Get(), const_cast<OTAsymmetricKey *>(&(pNym->GetPublicEncrKey()))));
    }
Пример #6
0
// The above method uses this one internally...
bool OTAccount::GenerateNewAccount(const OTPseudonym & theServer, const OTMessage & theMessage,
								   const OTAccount::AccountType eAcctType/*=OTAccount::simple*/)
{
	// First we generate a secure random number into a binary object.
	OTPayload thePayload;
	thePayload.SetPayloadSize(100);
	if (!RAND_bytes((unsigned char*)thePayload.GetPayloadPointer(), 100)) 
	{
		OTLog::Error("The PRNG is not seeded!\n");
//		abort(  );
		return false;	
	}
	
	// Next we calculate that binary object into a message digest (an OTIdentifier).
	OTIdentifier newID;
	if (!newID.CalculateDigest(thePayload))
	{
		OTLog::Error("Error generating new account ID.\n");
		return false;	
	}		
	
	// Next we get that digest (which is a binary hash number)
	// and extract a human-readable standard string format of that hash,
	// into an OTString.
	OTString strID(newID);
	
	SetRealAccountID(newID);		// Set the account number based on what we just generated.
	SetPurportedAccountID(newID);	// Might as well set them both. (Safe here to do so, for once.)

	m_strName.Set(strID); // So it's not blank. The user can always change it.
	
	// Next we create the full path filename for the account using the ID.
	m_strFilename.Format("%s%s%s%s%s", OTLog::Path(), OTLog::PathSeparator(),
						 OTLog::AccountFolder(),
						 OTLog::PathSeparator(), strID.Get());
	
	// Then we try to load it, in order to make sure that it doesn't already exist.
	if (LoadContractRawFile())
	{
		OTLog::Error("Error generating new account ID, account already exists.\n");
		return false;	
	}

	// Set up the various important starting values of the account.
	m_AcctType = eAcctType; // account type defaults to OTAccount::simple. But there are also issuer accts...
	
	// for basket accounts, the server is the user.
	if (OTAccount::basket == eAcctType)
	{
		theServer.GetIdentifier(m_AcctUserID);
	}
	else 
	{
		m_AcctUserID.SetString(theMessage.m_strNymID);
	}

	m_AcctAssetTypeID.SetString(theMessage.m_strAssetID);
	
	OTString TEMPstr(m_AcctAssetTypeID);

	OTLog::vOutput(3, "Creating new account, type:\n%s\nChanged to ID then back to string:\n%s\n", 
				   theMessage.m_strAssetID.Get(), TEMPstr.Get());
	
	OTIdentifier SERVER_ID(theMessage.m_strServerID);	
	SetRealServerID(SERVER_ID);			// todo this assumes the serverID on the message is correct. It's vetted, but still...
	SetPurportedServerID(SERVER_ID);

	const time_t tDate = time(NULL); // Today, now.
	m_BalanceDate.Format("%d", tDate);
		
	m_BalanceAmount.Set("0");
	
		
	// Sign the Account (so we know that we did)... Otherwise someone could put a fake
	// account file on the server if the code wasn't designed to verify the signature on the
	// account.
	SignContract(theServer);		
	SaveContract();		
	
	// Save the Account to storage (based on its ID.)
	SaveAccount();

	// Don't know why I had this here. Putting SaveAccount() instead.
//	OTString strFilename(m_strFilename);
//	SaveContract(strFilename.Get()); // Saves the account to a specific filename
	
	// No need to create the inbox and outbox ledgers...they will be created automatically
	// if they do not exist when they are needed.
	
	return true;
}
// The mint has a different key pair for each denomination.
// Pass the actual denomination such as 5, 10, 20, 50, 100...
bool OTMint_Lucre::AddDenomination(OTPseudonym & theNotary, int64_t lDenomination, int32_t nPrimeLength/*=1024*/)
{
    OT_ASSERT(NULL != m_pKeyPublic);
    
	bool bReturnValue = false;
	
	// Let's make sure it doesn't already exist
	OTASCIIArmor theArmor;
	if (GetPublic(theArmor, lDenomination))
	{
		// it already exists.
		OTLog::Error("Error: Denomination public already exists in OTMint::AddDenomination\n");
		return false;
	}
	if (GetPrivate(theArmor, lDenomination))
	{
		// it already exists.
		OTLog::Error("Error: Denomination private already exists in OTMint::AddDenomination\n");
		return false;
	}
	
	//		OTLog::Error("%s <size of bank prime in bits> <bank data file> <bank public data file>\n",
	
    if ((nPrimeLength/8) < (MIN_COIN_LENGTH+DIGEST_LENGTH))
	{
		OTLog::vError("Prime must be at least %d bits\n",
				(MIN_COIN_LENGTH+DIGEST_LENGTH)*8);
		return false;
	}
	
    if (nPrimeLength%8)
	{
		OTLog::Error("Prime length must be a multiple of 8\n");
		return false;
	}
	
#ifdef _WIN32
	SetMonitor("openssl.dump");
#else
	SetMonitor(stderr);
#endif
	
    OpenSSL_BIO bio		=	BIO_new(BIO_s_mem());
    OpenSSL_BIO bioPublic	=	BIO_new(BIO_s_mem());
	
	// Generate the mint private key information
    Bank bank(nPrimeLength/8);
    bank.WriteBIO(bio);
	
	// Generate the mint public key information
    PublicBank pbank(bank);
    pbank.WriteBIO(bioPublic);	
	
	// Copy from BIO back to a normal OTString or Ascii-Armor  
	char privateBankBuffer[4096], publicBankBuffer[4096];   // todo stop hardcoding these string lengths
	int32_t  privatebankLen	= BIO_read(bio, privateBankBuffer, 4000); // cutting it a little short on purpose, with the buffer.
	int32_t  publicbankLen	= BIO_read(bioPublic, publicBankBuffer, 4000); // Just makes me feel more comfortable for some reason.
	
	if (privatebankLen && publicbankLen)
	{
		// With this, we have the Lucre public and private bank info converted to OTStrings
		OTString strPublicBank;		strPublicBank.Set(publicBankBuffer, publicbankLen);
		OTString strPrivateBank;	strPrivateBank.Set(privateBankBuffer, privatebankLen);
		
		OTASCIIArmor * pPublic	= new OTASCIIArmor();
		OTASCIIArmor * pPrivate	= new OTASCIIArmor();
		
		OT_ASSERT(NULL != pPublic);
		OT_ASSERT(NULL != pPrivate);
		
		// Set the public bank info onto pPublic
		pPublic->SetString(strPublicBank, true); // linebreaks = true
		
		// Seal the private bank info up into an encrypted Envelope 
		// and set it onto pPrivate
		OTEnvelope theEnvelope;
		theEnvelope.Seal(theNotary, strPrivateBank);	// Todo check the return values on these two functions
		theEnvelope.GetAsciiArmoredData(*pPrivate);
		
		// Add the new key pair to the maps, using denomination as the key
		m_mapPublic[lDenomination]	= pPublic;
		m_mapPrivate[lDenomination]	= pPrivate;
		
		// Grab the Server Nym ID and save it with this Mint
		theNotary.GetIdentifier(m_ServerNymID);
        // ---------------------------
		// Grab the Server's public key and save it with this Mint
        //
        const OTAsymmetricKey & theNotaryPubKey = theNotary.GetPublicSignKey();
        delete m_pKeyPublic;
        m_pKeyPublic = theNotaryPubKey.ClonePubKey();
        // ---------------------------
		m_nDenominationCount++;
        // ---------------------------		
		// Success!
		bReturnValue = true;
		OTLog::vOutput(1, "Successfully added denomination: %lld\n", lDenomination);
	}
	
	return bReturnValue;
}
// The above method uses this one internally...
bool OTAccount::GenerateNewAccount(const OTPseudonym & theServer, const OTMessage & theMessage,
								   const OTAccount::AccountType eAcctType/*=OTAccount::simple*/,
								   int64_t lStashTransNum/*=0*/)
{
    const char *szFunc = "OTAccount::GenerateNewAccount";
    // -----------------------------------------------
	// First we generate a secure random number into a binary object...
    //
    OTPayload thePayload;

    if (false == thePayload.Randomize(100)) // todo hardcoding. Plus: is 100 bytes of random a little much here?
    {
		OTLog::vError("%s: Failed trying to acquire random numbers.\n", szFunc);
		return false;
    }
	// --------------------------------------------------
    //
	// Next we calculate that binary object into a message digest (an OTIdentifier).
    //
	OTIdentifier newID;
	if (!newID.CalculateDigest(thePayload))
	{
		OTLog::vError("%s: Error generating new account ID.\n", szFunc);
		return false;
	}
    // --------------------------------------------------
    //
	// Next we get that digest (which is a binary hash number)
	// and extract a human-readable standard string format of that hash,
	// into an OTString.
    //
	OTString strID(newID);

	SetRealAccountID(newID);		// Set the account number based on what we just generated.
	SetPurportedAccountID(newID);	// Might as well set them both. (Safe here to do so, for once.)

	m_strName.Set(strID); // So it's not blank. The user can always change it.

	// Next we create the full path filename for the account using the ID.
    //
	m_strFoldername = OTFolders::Account().Get();
	m_strFilename = strID.Get();

	// Then we try to load it, in order to make sure that it doesn't already exist.
	// --------------------------------------------------------------------

	if (OTDB::Exists(m_strFoldername.Get(), m_strFilename.Get()))
	{
		OTLog::vError("%s: Account already exists: %s\n", szFunc,
					  m_strFilename.Get());
		return false;
	}

	// --------------------------------------------------------------------

	// Set up the various important starting values of the account.
	m_AcctType = eAcctType; // account type defaults to OTAccount::simple. But there are also issuer accts...

	// --------------------------------------------------------------------

	if (IsInternalServerAcct())  // basket, basketsub, mint, voucher, and stash accounts are all "owned" by the server.
	{
		theServer.GetIdentifier(m_AcctUserID);
	}
	else
	{
		m_AcctUserID.SetString(theMessage.m_strNymID);
	}

	// --------------------------------------------------------------------
	m_AcctAssetTypeID.SetString(theMessage.m_strAssetID);

	OTLog::vOutput(3, "%s: Creating new account, type:\n%s\n", szFunc,
				   theMessage.m_strAssetID.Get());

	OTIdentifier SERVER_ID(theMessage.m_strServerID);
	SetRealServerID(SERVER_ID);			// todo this assumes the serverID on the message is correct. It's vetted, but still...
	SetPurportedServerID(SERVER_ID);

	const time64_t tDate = OTTimeGetCurrentTime(); // Today, now.
	m_BalanceDate.Format("%d", tDate);

	m_BalanceAmount.Set("0");

	// --------------------------------------------------------------------

	if (IsStashAcct())
	{
		OT_ASSERT_MSG(lStashTransNum > 0,
					  "You created a stash account, but with a zero-or-negative transaction number for its cron item.");
		m_lStashTransNum	= lStashTransNum;
	}

	// --------------------------------------------------------------------

	// Sign the Account (so we know that we did)... Otherwise someone could put a fake
	// account file on the server if the code wasn't designed to verify the signature on the
	// account.
	SignContract(theServer);
	SaveContract();

	// Save the Account to storage (based on its ID.)
	SaveAccount();

	// Don't know why I had this here. Putting SaveAccount() instead.
//	OTString strFilename(m_strFilename);
//	SaveContract(strFilename.Get()); // Saves the account to a specific filename

	// No need to create the inbox and outbox ledgers...they will be created automatically
	// if they do not exist when they are needed.

	return true;
}
// ----------------------------------------------------
// INTERNAL NYM: For adding a PASSPHRASE to a PURSE.
//
// What if you DON'T want to encrypt the purse to your Nym??
// What if you just want to use a passphrase instead?
// That's what these functions are for. OT just generates
// a dummy Nym and stores it INSIDE THE PURSE. You set the
// passphrase for the dummy nym, and thereafter your 
// experience is one of a password-protected purse. (But
// in reality, there is a dummy nym inside that purse.)
//
bool OTPurse::GenerateInternalNym(int nBits/*=1024*/)
{
    // -------------------------------------------    
    if (this->IsUsingATempNym() || (NULL != this->GetInternalNym()))
    {
        OTLog::Output(0, "OTPurse::GenerateInternalNym: Failed: internal Nym already exists.\n");
        return false;
    }
    // -------------------------------------------    
    if (!this->IsEmpty())
    {
        OTLog::Output(0, "OTPurse::GenerateInternalNym: Failed: The purse must be EMPTY before you create a new dummy Nym, internal to that purse. (For the purposes of adding a passphrase to the purse, normally.)\n");
        return false;
    }
    // -------------------------------------------
    
    switch (nBits) 
    {
        case 1024: // todo hardcoding.
        case 2048:
        case 4096:
        case 8192:
            break;            
        default:
            OTLog::vError("OTPurse::GenerateInternalNym: Failure: nBits must be one of: "
                          "1024, 2048, 4096, 8192. (%d was passed...)\n", nBits);
            return false;
    }

    // -------------------------------------------
    OTPseudonym * pNym = new OTPseudonym;
	OT_ASSERT_MSG(NULL != pNym, "OTPurse::GenerateInternalNym: Assert: NULL != new OTPseudonym \n");
    // BELOW THIS point, pNym must either be deleted, or saved. (Or it will leak.)
	// --------------------------------------------
    
	if (false == pNym->GenerateNym(nBits, false)) // bCreateFile=true by default, but it's FALSE here.
	{
        OTLog::vOutput(0, "OTPurse::GenerateInternalNym: Failed while calling pNym->GenerateNym(%d, false).\n",
                       nBits);
		delete pNym;
		pNym = NULL;
        
        return false;
	}
    // ---------------------------------------------------
    
    const bool bSetIdentifier = pNym->SetIdentifierByPubkey();
    OT_ASSERT_MSG(bSetIdentifier, "OTPurse::GenerateInternalNym: Assert: pNym->SetIdentifierByPubkey() \n");

    // By this point, the Nym has been successfully generated, and he even has an ID!
    // Let's attach him to the purse so we don't have to keep worrying about a leak.
    //
    m_pTempNym = pNym;
    // ----------------------------
    // Let's make sure the new Nym's ID is also this purse's UserID...
    //
    pNym->GetIdentifier(m_UserID);
    
    m_bUsingTempNym    = true;
    m_bIsNymIDIncluded = true;
    
	return true;
}
Пример #10
0
// THIS FUNCTION IS CALLED BY THE CUSTOMER
//
// (Transaction number and closing number are retrieved from Nym at this time.)
bool OTAgreement::Confirm(OTPseudonym & MERCHANT_NYM, OTPseudonym & PAYER_NYM)
{
    // ----------------------------------------------------------------------------
    OTIdentifier id_MERCHANT_NYM, id_PAYER_NYM;
    MERCHANT_NYM.GetIdentifier(id_MERCHANT_NYM);
    PAYER_NYM.GetIdentifier(id_PAYER_NYM);
    
    if (GetRecipientUserID() == GetSenderUserID())
    {
        OTLog::Output(0, "OTAgreement::Confirm: Error: Sender and recipient have the same Nym ID (not allowed.)\n");
        return false;        
    }
    else if (GetRecipientUserID() != id_MERCHANT_NYM)
    {
        OTLog::Output(0, "OTAgreement::Confirm: Merchant has wrong NymID (should be same as RecipientUserID.)\n");
        return false;        
    }
    else if (GetSenderUserID() != id_PAYER_NYM)
    {
        OTLog::Output(0, "OTAgreement::Confirm: Payer has wrong NymID (should be same as SenderUserID.)\n");
        return false;        
    }
    else if (PAYER_NYM.GetTransactionNumCount(GetServerID()) < 2) // Need opening and closing numbers (that's 2)... 
    {
        OTLog::Output(0, "OTAgreement::Confirm: Failure. You need at least 2 transaction numbers available to do this.\n");
		return false;
    }
    else if (GetRecipientCountClosingNumbers() < 2)
    {
        OTLog::Output(0, "OTAgreement::Confirm: Failure. (The merchant was supposed to attach 2 transaction numbers.)\n");
		return false;
    }
    // ----------------------------------------------------------------------------
    // This is the single reason why MERCHANT_NYM was even passed in here!
    // Supposedly merchant has already signed.  Let's verify this!!
    //
    if (false == this->VerifySignature(MERCHANT_NYM))
    {
        OTLog::Output(0, "OTAgreement::Confirm: Merchant's signature failed to verify.\n");
        return false;
    }
    // ----------------------------------------------------------------------------
    
    // Now that we KNOW the merchant signed it... SAVE MERCHANT's COPY.
    // Let's save a copy of the one the merchant signed, before changing it and re-signing it,
    // (to add my own transaction numbers...)
    //
    OTString strTemp;
    this->SaveContractRaw(strTemp);
    this->SetMerchantSignedCopy(strTemp);

    // *******************************************************************
    
    // The payer has to submit TWO transaction numbers in order to activate this agreement...
    //
    OTString strServerID(GetServerID());
	long lTransactionNumber=0, lClosingTransactionNo=0;
	
	if (false == PAYER_NYM.GetNextTransactionNum(PAYER_NYM, strServerID, lTransactionNumber))
	{
		OTLog::Error("OTAgreement::Confirm: Error: Strangely unable to get a transaction number.\n");
		return false;
	}
	else if (false == PAYER_NYM.GetNextTransactionNum(PAYER_NYM, strServerID, lClosingTransactionNo))
	{
 		OTLog::Error("OTAgreement::Confirm: Error: Strangely unable to get a closing transaction number.\n");
        PAYER_NYM.AddTransactionNum(PAYER_NYM, strServerID, lTransactionNumber, true); // bSave=true
        // (Since the first one was successful, we just put it back before returning.)
		return false;
	}
    
    // At this point we now HAVE 2 transaction numbers (for payer / sender)...
    // We can't return without USING THEM or PUTTING THEM BACK.
    //
    // ---------------------------------------------------------
 
     
	this->SetTransactionNum(lTransactionNumber); // Set the Transaction Number
    this->AddClosingTransactionNo(lClosingTransactionNo); // and the Closing Number (both for sender)...

	// ------------------------------------------- 
	
    // CREATION DATE was set in the Merchant's proposal, and it's RESET here in the Confirm.
    // This way, (since we still have the original proposal) we can see BOTH times.
    //
	time_t CURRENT_TIME = time(NULL);
	// Set the Creation Date.
	SetCreationDate(CURRENT_TIME);
    
	// ------------------------------------------- 
    
	OTLog::Output(4, "OTAgreement::Confirm(): Success!\n");
    
	return true;
}
Пример #11
0
// THIS FUNCTION IS CALLED BY THE MERCHANT
//
// (lMerchantTransactionNumber, lMerchantClosingNumber are set internally in this call, from MERCHANT_NYM.)
bool OTAgreement::SetProposal(OTPseudonym & MERCHANT_NYM,       const OTString & strConsideration,
                              const time_t VALID_FROM/*=0*/,  const time_t VALID_TO/*=0*/)
{
    // ----------------------------------------------------------------------------
    OTIdentifier id_MERCHANT_NYM;
    MERCHANT_NYM.GetIdentifier(id_MERCHANT_NYM);
    
    if (GetRecipientUserID() != id_MERCHANT_NYM)
    {
        OTLog::Output(0, "OTAgreement::SetProposal: Merchant has wrong NymID (should be same as RecipientUserID.)\n");
        return false;        
    }
    else if (GetRecipientUserID() == GetSenderUserID())
    {
        OTLog::Output(0, "OTAgreement::SetProposal: Error: Sender and recipient have the same Nym ID (not allowed.)\n");
        return false;        
    }
    else if (MERCHANT_NYM.GetTransactionNumCount(GetServerID()) < 2) // Need opening and closing numbers (that's 2)... 
    {
        OTLog::Output(0, "OTAgreement::SetProposal: Failure. You need at least 2 transaction numbers available to do this.\n");
		return false;
    }

    // ------------------------------------------- 
	// Set the CREATION DATE
    //
	const time_t CURRENT_TIME = time(NULL);
	
	// Set the Creation Date.
	SetCreationDate(CURRENT_TIME);

    // -----------------------------------------
    // Putting this above here so I don't have to put the transaction numbers back if this fails:
    // ------------------------------------------- 
    // VALID_FROM
    //
	// The default "valid from" time is NOW.
	if (0 >= VALID_FROM) // if it's 0 or less, set to current time.
		SetValidFrom(CURRENT_TIME);
	else // Otherwise use whatever was passed in.
		SetValidFrom(VALID_FROM);
    // ------------------------------------------- 
    // VALID_TO
    //
	// The default "valid to" time is 0 (which means no expiration date / cancel anytime.)
	if (0 == VALID_TO) // VALID_TO is 0
	{
		SetValidTo(VALID_TO); // Keep it at zero then, so it won't expire.
	}
	else if (0 < VALID_TO) // VALID_TO is ABOVE zero...
	{
		if (VALID_TO < VALID_FROM) // If Valid-To date is EARLIER than Valid-From date...
		{
			long lValidTo = VALID_TO, lValidFrom = VALID_FROM;
			OTLog::vError("OTAgreement::SetProposal: VALID_TO (%ld) is earlier than VALID_FROM (%ld)\n", 
                          lValidTo, lValidFrom);
			return false;
		}
		
		SetValidTo(VALID_TO); // Set it to whatever it is, since it is now validated as higher than Valid-From.
	}
	else // VALID_TO is a NEGATIVE number... Error.
	{
		long lValidTo = VALID_TO;
		OTLog::vError("Negative value for valid_to in SetAgreement: %ld\n", lValidTo);
        
		return false;
	}

    // ----------------------------------------------------------------------------
    // Since we'll be needing 2 transaction numbers to do this, let's grab 'em...
    //
    OTString strServerID(GetServerID());
    
	long lTransactionNumber=0, lClosingTransactionNo=0;
	
    if (MERCHANT_NYM.GetTransactionNumCount(GetServerID()) < 2) // Need opening and closing numbers (that's 2)... 
    {
        OTLog::Output(0, "OTAgreement::SetProposal: Failure. You need at least 2 transaction numbers available to do this.\n");
		return false;
    }
	else if (false == MERCHANT_NYM.GetNextTransactionNum(MERCHANT_NYM, strServerID, lTransactionNumber))
	{
		OTLog::Error("OTAgreement::SetProposal: Error: Strangely unable to get a transaction number.\n");
		return false;
	}
	else if (false == MERCHANT_NYM.GetNextTransactionNum(MERCHANT_NYM, strServerID, lClosingTransactionNo))
	{
 		OTLog::Error("OTAgreement::SetProposal: Error: Strangely unable to get a closing transaction number.\n");
        MERCHANT_NYM.AddTransactionNum(MERCHANT_NYM, strServerID, lTransactionNumber, true); // bSave=true
        // (Since the first one was successful, we just put it back before returning.)
		return false;
	}

    // At this point we now have 2 transaction numbers...
    // We can't return without either USING THEM, or PUTTING THEM BACK.
    //
    // ---------------------------------------------------------
    
	// Set the Transaction Number and the Closing transaction number... (for merchant / recipient.)
    //
    this->AddRecipientClosingTransactionNo(lTransactionNumber);
    this->AddRecipientClosingTransactionNo(lClosingTransactionNo);
    // (They just both go onto this same list.)
    
	// ------------------------------------------- 

	// Set the Consideration memo...
	m_strConsideration.Set(strConsideration);
            
	// ------------------------------------------- 
    
	OTLog::Output(4, "Successfully performed OTPaymentPlan::SetProposal()\n");
	
	return true;
}