Many fixes and clean up for NewcoinAddress.

This commit is contained in:
Arthur Britto
2012-03-18 19:00:28 -07:00
parent 73ff9b95ad
commit 925cc6ff86
11 changed files with 159 additions and 240 deletions

View File

@@ -36,14 +36,6 @@ static bool isHex(char j)
return false;
}
bool AccountState::isHexAccountID(const std::string& acct)
{
if(acct.size()!=40) return false;
for(int i=1; i<40; i++)
if(!isHex(acct[i])) return false;
return true;
}
void AccountState::addJson(Json::Value& val)
{
Json::Value as(Json::objectValue);

View File

@@ -51,8 +51,7 @@ public:
assert(mAccountSeq!=0);
mAccountSeq--;
}
static bool isHexAccountID(const std::string& acct);
std::vector<unsigned char> getRaw() const;
void addJson(Json::Value& value);
};

View File

@@ -71,18 +71,17 @@ void Application::run()
mRPCDoor=new RPCDoor(mIOService);
}//else BOOST_LOG_TRIVIAL(info) << "No RPC Port set. Not listening for commands.";
mConnectionPool.connectToNetwork(mKnownNodes, mIOService);
mConnectionPool.connectToNetwork(mKnownNodes, mIOService);
mTimingService.start(mIOService);
std::cout << "Before Run." << std::endl;
// Temporary root account will be ["This is my payphrase."]:0
NewcoinAddress rootFamilySeed;
rootFamilySeed.setFamilySeed(CKey::PassPhraseToKey("This is my payphrase"));
NewcoinAddress rootFamilyGenerator;
rootFamilyGenerator.setFamilyGenerator(rootFamilySeed);
NewcoinAddress rootFamilySeed; // Hold the 128 password.
NewcoinAddress rootFamilyGenerator; // Hold the generator.
NewcoinAddress rootAddress;
rootFamilySeed.setFamilySeed(CKey::PassPhraseToKey("This is my payphrase"));
rootFamilyGenerator.setFamilyGenerator(rootFamilySeed);
rootAddress.setAccountPublic(rootFamilyGenerator, 0);
Ledger::pointer firstLedger(new Ledger(rootAddress, 100000000));

View File

@@ -143,14 +143,14 @@ EC_KEY* CKey::GenerateRootPubKey(BIGNUM* pubGenerator)
}
// --> public generator
static BIGNUM* makeHash(const NewcoinAddress& family, int seq, BIGNUM* order)
static BIGNUM* makeHash(const NewcoinAddress& generator, int seq, BIGNUM* order)
{
int subSeq=0;
BIGNUM* ret=NULL;
do
{
Serializer s((128+32+32)/8);
s.add128(family.getFamilySeed());
Serializer s((33*8+32+32)/8);
s.addRaw(generator.getFamilyGenerator());
s.add32(seq);
s.add32(subSeq++);
uint256 root=s.getSHA512Half();
@@ -163,9 +163,9 @@ static BIGNUM* makeHash(const NewcoinAddress& family, int seq, BIGNUM* order)
}
// --> public generator
EC_KEY* CKey::GeneratePublicDeterministicKey(const NewcoinAddress& family, int seq)
EC_KEY* CKey::GeneratePublicDeterministicKey(const NewcoinAddress& generator, int seq)
{ // publicKey(n) = rootPublicKey EC_POINT_+ Hash(pubHash|seq)*point
EC_KEY* rootKey = CKey::GenerateRootPubKey(family.getFamilyGeneratorBN());
EC_KEY* rootKey = CKey::GenerateRootPubKey(generator.getFamilyGeneratorBN());
const EC_POINT* rootPubKey = EC_KEY_get0_public_key(rootKey);
BN_CTX* ctx = BN_CTX_new();
EC_KEY* pkey = EC_KEY_new_by_curve_name(NID_secp256k1);
@@ -193,7 +193,7 @@ EC_KEY* CKey::GeneratePublicDeterministicKey(const NewcoinAddress& family, int s
// Calculate the private additional key.
if (success) {
hash = makeHash(family, seq, order);
hash = makeHash(generator, seq, order);
if(!hash) success = false;
}

View File

@@ -5,10 +5,12 @@
#include "openssl/ec.h"
#include <cassert>
#include <algorithm>
#include <iostream>
NewcoinAddress::NewcoinAddress()
{
version = VER_NONE;
nVersion = VER_NONE;
}
bool NewcoinAddress::IsValid()
@@ -22,34 +24,30 @@ bool NewcoinAddress::IsValid()
uint160 NewcoinAddress::getHanko() const
{
switch (version) {
switch (nVersion) {
case VER_NONE:
std::runtime_error("unset source");
break;
throw std::runtime_error("unset source");
case VER_HANKO:
return uint160(vchData);
return uint160(vchData);
case VER_NODE_PUBLIC:
// Note, we are encoding the left or right.
return Hash160(vchData);
// Note, we are encoding the left or right.
return Hash160(vchData);
default:
std::runtime_error("bad source");
throw std::runtime_error("bad source");
}
return 0;
}
std::string NewcoinAddress::humanHanko() const
{
switch (version) {
switch (nVersion) {
case VER_NONE:
std::runtime_error("unset source");
break;
throw std::runtime_error("unset source");
case VER_HANKO:
return ToString();
return ToString();
case VER_NODE_PUBLIC:
{
@@ -61,10 +59,8 @@ std::string NewcoinAddress::humanHanko() const
}
default:
std::runtime_error("bad source");
throw std::runtime_error("bad source");
}
return 0;
}
bool NewcoinAddress::setHanko(const std::string& strHanko)
@@ -83,45 +79,36 @@ void NewcoinAddress::setHanko(const uint160& hash160)
const std::vector<unsigned char>& NewcoinAddress::getNodePublic() const
{
switch (version) {
switch (nVersion) {
case VER_NONE:
std::runtime_error("unset source");
break;
throw std::runtime_error("unset source");
case VER_HANKO:
std::runtime_error("public not available from hanko");
break;
throw std::runtime_error("public not available from hanko");
case VER_NODE_PUBLIC:
// Do nothing.
break;
return vchData;
default:
std::runtime_error("bad source");
throw std::runtime_error("bad source");
}
return vchData;
}
std::string NewcoinAddress::humanNodePublic() const
{
switch (version) {
switch (nVersion) {
case VER_NONE:
std::runtime_error("unset source");
break;
throw std::runtime_error("unset source");
case VER_HANKO:
std::runtime_error("public not available from hanko");
break;
throw std::runtime_error("public not available from hanko");
case VER_NODE_PUBLIC:
return ToString();
return ToString();
default:
std::runtime_error("bad source");
throw std::runtime_error("bad source");
}
return 0;
}
bool NewcoinAddress::setNodePublic(const std::string& strPublic)
@@ -140,37 +127,30 @@ void NewcoinAddress::setNodePublic(const std::vector<unsigned char>& vPublic)
uint256 NewcoinAddress::getNodePrivate() const
{
switch (version) {
switch (nVersion) {
case VER_NONE:
std::runtime_error("unset source");
break;
throw std::runtime_error("unset source");
case VER_NODE_PRIVATE:
// Nothing
break;
return uint256(vchData);
default:
std::runtime_error("bad source");
throw std::runtime_error("bad source");
}
return uint256(vchData);
}
std::string NewcoinAddress::humanNodePrivate() const
{
switch (version) {
switch (nVersion) {
case VER_NONE:
std::runtime_error("unset source");
break;
throw std::runtime_error("unset source");
case VER_NODE_PRIVATE:
return ToString();
default:
std::runtime_error("bad source");
throw std::runtime_error("bad source");
}
return 0;
}
bool NewcoinAddress::setNodePrivate(const std::string& strPrivate)
@@ -189,10 +169,9 @@ void NewcoinAddress::setNodePrivate(uint256 hash256)
uint160 NewcoinAddress::getAccountID() const
{
switch (version) {
switch (nVersion) {
case VER_NONE:
std::runtime_error("unset source");
break;
throw std::runtime_error("unset source");
case VER_ACCOUNT_ID:
return uint160(vchData);
@@ -202,18 +181,15 @@ uint160 NewcoinAddress::getAccountID() const
return Hash160(vchData);
default:
std::runtime_error("bad source");
throw std::runtime_error("bad source");
}
return 0;
}
std::string NewcoinAddress::humanAccountID() const
{
switch (version) {
switch (nVersion) {
case VER_NONE:
std::runtime_error("unset source");
break;
throw std::runtime_error("unset source");
case VER_ACCOUNT_ID:
return ToString();
@@ -222,16 +198,14 @@ std::string NewcoinAddress::humanAccountID() const
{
NewcoinAddress accountID;
(void) accountID.setHanko(getAccountID());
(void) accountID.setAccountID(getAccountID());
return accountID.ToString();
}
default:
std::runtime_error("bad source");
throw std::runtime_error("bad source");
}
return 0;
}
bool NewcoinAddress::setAccountID(const std::string& strAccountID)
@@ -250,45 +224,37 @@ void NewcoinAddress::setAccountID(const uint160& hash160)
const std::vector<unsigned char>& NewcoinAddress::getAccountPublic() const
{
switch (version) {
switch (nVersion) {
case VER_NONE:
std::runtime_error("unset source");
break;
throw std::runtime_error("unset source");
case VER_ACCOUNT_ID:
std::runtime_error("public not available from account id");
throw std::runtime_error("public not available from account id");
break;
case VER_ACCOUNT_PUBLIC:
// Do nothing.
break;
return vchData;
default:
std::runtime_error("bad source");
throw std::runtime_error("bad source");
}
return vchData;
}
std::string NewcoinAddress::humanAccountPublic() const
{
switch (version) {
switch (nVersion) {
case VER_NONE:
std::runtime_error("unset source");
break;
throw std::runtime_error("unset source");
case VER_ACCOUNT_ID:
std::runtime_error("public not available from account id");
break;
throw std::runtime_error("public not available from account id");
case VER_ACCOUNT_PUBLIC:
return ToString();
default:
std::runtime_error("bad source");
throw std::runtime_error("bad source");
}
return 0;
}
bool NewcoinAddress::setAccountPublic(const std::string& strPublic)
@@ -314,37 +280,30 @@ void NewcoinAddress::setAccountPublic(const NewcoinAddress& generator, int seq)
uint256 NewcoinAddress::getAccountPrivate() const
{
switch (version) {
switch (nVersion) {
case VER_NONE:
std::runtime_error("unset source");
break;
throw std::runtime_error("unset source");
case VER_ACCOUNT_PRIVATE:
// Do nothing.
break;
return uint256(vchData);
default:
std::runtime_error("bad source");
throw std::runtime_error("bad source");
}
return uint256(vchData);
}
std::string NewcoinAddress::humanAccountPrivate() const
{
switch (version) {
switch (nVersion) {
case VER_NONE:
std::runtime_error("unset source");
break;
throw std::runtime_error("unset source");
case VER_ACCOUNT_PRIVATE:
return ToString();
default:
std::runtime_error("bad source");
throw std::runtime_error("bad source");
}
return 0;
}
bool NewcoinAddress::setAccountPrivate(const std::string& strPrivate)
@@ -363,63 +322,53 @@ void NewcoinAddress::setAccountPrivate(uint256 hash256)
BIGNUM* NewcoinAddress::getFamilyGeneratorBN() const
{
switch (version) {
switch (nVersion) {
case VER_NONE:
std::runtime_error("unset source");
break;
throw std::runtime_error("unset source");
case VER_FAMILY_GENERATOR:
// Do nothing.
break;
default:
std::runtime_error("bad source");
throw std::runtime_error("bad source");
}
// Convert to big-endian
unsigned char be[vchData.size()];
BIGNUM* ret = BN_bin2bn(&vchData[0], vchData.size(), NULL);
assert(ret);
std::reverse_copy(vchData.begin(), vchData.end(), &be[0]);
return BN_bin2bn(be, vchData.size(), NULL);
return ret;
}
const std::vector<unsigned char>& NewcoinAddress::getFamilyGenerator() const
{
switch (version) {
switch (nVersion) {
case VER_NONE:
std::runtime_error("unset source");
break;
throw std::runtime_error("unset source");
case VER_FAMILY_GENERATOR:
// Do nothing.
break;
return vchData;
default:
std::runtime_error("bad source");
throw std::runtime_error("bad source");
}
return vchData;
}
std::string NewcoinAddress::humanFamilyGenerator() const
{
switch (version) {
switch (nVersion) {
case VER_NONE:
std::runtime_error("unset source");
break;
throw std::runtime_error("unset source");
case VER_FAMILY_GENERATOR:
return ToString();
return ToString();
default:
std::runtime_error("bad source");
throw std::runtime_error("bad source");
}
return 0;
}
// YYY Would be nice if you could pass a family seed and derive the generator.
bool NewcoinAddress::setFamilyGenerator(const std::string& strGenerator)
{
return SetString(strGenerator.c_str(), VER_FAMILY_GENERATOR);
@@ -439,42 +388,31 @@ void NewcoinAddress::setFamilyGenerator(const NewcoinAddress& seed)
// Family Seed
//
// --> dstGenerator: Set the public generator from our seed.
void NewcoinAddress::seedInfo(NewcoinAddress* dstGenerator, BIGNUM** dstPrivateKey) const
{
// Generate root key
EC_KEY *base=CKey::GenerateRootDeterministicKey(getFamilySeed());
CKey pubkey = CKey(getFamilySeed());
// Extract family name
std::vector<unsigned char> rootPubKey(33, 0);
unsigned char *begin=&rootPubKey[0];
i2o_ECPublicKey(base, &begin);
while(rootPubKey.size()<33) rootPubKey.push_back((unsigned char)0);
if (dstGenerator)
dstGenerator->setFamilyGenerator(rootPubKey);
if (dstGenerator) {
dstGenerator->setFamilyGenerator(pubkey.GetPubKey());
}
if (dstPrivateKey)
*dstPrivateKey = BN_dup(EC_KEY_get0_private_key(base));
EC_KEY_free(base);
*dstPrivateKey = pubkey.GetSecretBN();
}
uint128 NewcoinAddress::getFamilySeed() const
{
switch (version) {
switch (nVersion) {
case VER_NONE:
std::runtime_error("unset source");
break;
throw std::runtime_error("unset source");
case VER_FAMILY_SEED:
// Do nothing.
break;
return uint128(vchData);
default:
std::runtime_error("bad source");
throw std::runtime_error("bad source");
}
return uint128(vchData);
}
BIGNUM* NewcoinAddress::getFamilyPrivateKey() const
@@ -488,19 +426,16 @@ BIGNUM* NewcoinAddress::getFamilyPrivateKey() const
std::string NewcoinAddress::humanFamilySeed() const
{
switch (version) {
switch (nVersion) {
case VER_NONE:
std::runtime_error("unset source");
break;
throw std::runtime_error("unset source");
case VER_FAMILY_SEED:
return ToString();
default:
std::runtime_error("bad source");
throw std::runtime_error("bad source");
}
return 0;
}
bool NewcoinAddress::setFamilySeed(const std::string& strSeed)

View File

@@ -12,18 +12,16 @@ class NewcoinAddress : public CBase58Data
private:
typedef enum {
VER_NONE = 1,
VER_HANKO = 13,
VER_NODE_PUBLIC = 18,
VER_NODE_PRIVATE = 23,
VER_HANKO = 8,
VER_NODE_PUBLIC = 28,
VER_NODE_PRIVATE = 32,
VER_ACCOUNT_ID = 0,
VER_ACCOUNT_PUBLIC = 3,
VER_ACCOUNT_PRIVATE = 8,
VER_FAMILY_GENERATOR = 28,
VER_ACCOUNT_PUBLIC = 35,
VER_ACCOUNT_PRIVATE = 34,
VER_FAMILY_GENERATOR = 41,
VER_FAMILY_SEED = 33,
} VersionEncoding;
VersionEncoding version;
void seedInfo(NewcoinAddress* dstGenerator, BIGNUM** dstPrivateKey) const;
public:
@@ -32,43 +30,63 @@ public:
bool IsValid();
//
// Nodes
// hanko
//
uint160 getHanko() const;
const std::vector<unsigned char>& getNodePublic() const;
uint256 getNodePrivate() const;
std::string humanHanko() const;
std::string humanNodePublic() const;
std::string humanNodePrivate() const;
bool setHanko(const std::string& strHanko);
void setHanko(const uint160& hash160);
//
// Node Public
//
const std::vector<unsigned char>& getNodePublic() const;
std::string humanNodePublic() const;
bool setNodePublic(const std::string& strPublic);
void setNodePublic(const std::vector<unsigned char>& vPublic);
//
// Node Private
//
uint256 getNodePrivate() const;
std::string humanNodePrivate() const;
bool setNodePrivate(const std::string& strPrivate);
void setNodePrivate(uint256 hash256);
//
// Accounts
// Accounts IDs
//
uint160 getAccountID() const;
const std::vector<unsigned char>& getAccountPublic() const;
uint256 getAccountPrivate() const;
std::string humanAccountID() const;
std::string humanAccountPublic() const;
std::string humanAccountPrivate() const;
bool setAccountID(const std::string& strAccountID);
void setAccountID(const uint160& hash160In);
//
// Accounts Public
//
const std::vector<unsigned char>& getAccountPublic() const;
std::string humanAccountPublic() const;
bool setAccountPublic(const std::string& strPublic);
void setAccountPublic(const std::vector<unsigned char>& vPublic);
void setAccountPublic(const NewcoinAddress& generator, int seq);
//
// Accounts Private
//
uint256 getAccountPrivate() const;
std::string humanAccountPrivate() const;
bool setAccountPrivate(const std::string& strPrivate);
void setAccountPrivate(uint256 hash256);
@@ -89,7 +107,9 @@ public:
//
uint128 getFamilySeed() const;
BIGNUM* getFamilyPrivateKey() const;
std::string humanFamilySeed() const;
bool setFamilySeed(const std::string& strSeed);
void setFamilySeed(uint128 hash128);
};

View File

@@ -361,20 +361,20 @@ void Wallet::load()
if(!db->startIterRows()) return;
while(db->getNextRow())
{
std::string familyGenerator, comment;
db->getStr("FamilyGenerator", familyGenerator);
std::string generator, comment;
db->getStr("FamilyGenerator", generator);
db->getStr("Comment", comment);
int seq=db->getBigInt("Seq");
NewcoinAddress family;
NewcoinAddress familyGenerator;
family.setFamilyGenerator(familyGenerator);
familyGenerator.setFamilyGenerator(generator);
LocalAccountFamily::pointer f(doPublic(family, true, false));
LocalAccountFamily::pointer f(doPublic(familyGenerator, true, false));
if(f)
{
// XXX Compare could be better.
assert(f->getFamily().getFamilyGenerator()==family.getFamilyGenerator());
assert(f->getFamily().getFamilyGenerator()==familyGenerator.getFamilyGenerator());
f->setSeq(seq);
f->setComment(comment);
}
@@ -515,57 +515,34 @@ void Wallet::delFamily(const NewcoinAddress& familyName)
// --> pubKey: hex
// --> do_create: Add to mFamilies
// --> do_db: write out db
LocalAccountFamily::pointer Wallet::doPublic(const NewcoinAddress& pubKey, bool do_create, bool do_db)
LocalAccountFamily::pointer Wallet::doPublic(const NewcoinAddress& familyGenerator, bool do_create, bool do_db)
{
// Generate root key
EC_KEY *pkey=CKey::GenerateRootPubKey(pubKey.getFamilyGeneratorBN());
if(!pkey)
{
#ifdef DEBUG
std::cerr << "Unable to generate root public key" << std::endl;
assert(false);
#endif
return LocalAccountFamily::pointer();
}
// Extract family name
std::vector<unsigned char> rootPubKey(33, 0);
unsigned char *begin=&rootPubKey[0];
i2o_ECPublicKey(pkey, &begin);
while(rootPubKey.size()<33) rootPubKey.push_back((unsigned char)0);
NewcoinAddress family;
family.setFamilyGenerator(rootPubKey);
boost::recursive_mutex::scoped_lock sl(mLock);
std::map<NewcoinAddress, LocalAccountFamily::pointer>::iterator fit=mFamilies.find(family);
std::map<NewcoinAddress, LocalAccountFamily::pointer>::iterator fit=mFamilies.find(familyGenerator);
if(fit!=mFamilies.end()) // already added
{
EC_KEY_free(pkey);
return fit->second;
}
if(!do_create)
{
EC_KEY_free(pkey);
return LocalAccountFamily::pointer();
}
LocalAccountFamily::pointer fam;
if(do_db)
{
fam=LocalAccountFamily::readFamily(family);
fam=LocalAccountFamily::readFamily(familyGenerator);
if(fam) do_create=false;
}
if(do_create)
{
fam=boost::make_shared<LocalAccountFamily>(family);
mFamilies.insert(std::make_pair(family, fam));
fam=boost::make_shared<LocalAccountFamily>(familyGenerator);
mFamilies.insert(std::make_pair(familyGenerator, fam));
if(do_db) fam->write(true);
}
sl.unlock();
EC_KEY_free(pkey);
return fam;
}

View File

@@ -31,7 +31,7 @@ protected:
uint32 mLedger; // ledger we last synched to
LocalAccountFamily::pointer doPrivate(const NewcoinAddress& familySeed, bool do_create, bool do_unlock);
LocalAccountFamily::pointer doPublic(const NewcoinAddress& pubKey, bool do_create, bool do_db);
LocalAccountFamily::pointer doPublic(const NewcoinAddress& familyGenerator, bool do_create, bool do_db);
// void addFamily(const NewcoinAddress& family, const std::string& pubKey, int seq, const std::string& name, const std::string& comment);

View File

@@ -18,12 +18,11 @@
#include <string>
#include <algorithm>
#include <vector>
#include "bignum.h"
#include "BitcoinUtil.h"
// Bitcoin's encoding: "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
static const char* pszBase58 = "sa3A5h7n9NBfDFEGHJKLM4PQRSTUVWXYZ2bcdeCg6ijkm8opqr1tuvwxyz";
static const char* pszBase58 = "ipshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqr1tuvAxyz";
inline std::string EncodeBase58(const unsigned char* pbegin, const unsigned char* pend)
{
@@ -158,10 +157,6 @@ inline bool DecodeBase58Check(const std::string& str, std::vector<unsigned char>
}
class CBase58Data
{
protected:
@@ -226,7 +221,9 @@ public:
std::string ToString() const
{
std::vector<unsigned char> vch(1, nVersion);
vch.insert(vch.end(), vchData.begin(), vchData.end());
vch.insert(vch.end(), vchData.begin(), vchData.end());
return EncodeBase58Check(vch);
}
@@ -246,6 +243,4 @@ public:
bool operator> (const CBase58Data& b58) const { return CompareTo(b58) > 0; }
};
#endif

View File

@@ -126,7 +126,7 @@ public:
static uint128 PassPhraseToKey(const std::string& passPhrase);
static EC_KEY* GenerateRootDeterministicKey(const uint128& passPhrase);
static EC_KEY* GenerateRootPubKey(BIGNUM* pubGenerator);
static EC_KEY* GeneratePublicDeterministicKey(const NewcoinAddress& family, int n);
static EC_KEY* GeneratePublicDeterministicKey(const NewcoinAddress& generator, int n);
static EC_KEY* GeneratePrivateDeterministicKey(const NewcoinAddress& family, const BIGNUM* rootPriv, int n);
CKey(const uint128& passPhrase) : fSet(true)
@@ -135,9 +135,9 @@ public:
assert(pkey);
}
CKey(const NewcoinAddress& base, int n) : fSet(true)
CKey(const NewcoinAddress& generator, int n) : fSet(true)
{ // public deterministic key
pkey = GeneratePublicDeterministicKey(base, n);
pkey = GeneratePublicDeterministicKey(generator, n);
assert(pkey);
}
@@ -199,7 +199,12 @@ public:
int n=BN_bn2bin(bn, &vchRet[32 - nBytes]);
if (n != nBytes)
throw key_error("CKey::GetSecret(): BN_bn2bin failed");
return vchRet;
return vchRet;
}
BIGNUM* GetSecretBN() const
{
return BN_dup(EC_KEY_get0_private_key(pkey));
}
CPrivKey GetPrivKey() const

View File

@@ -5,7 +5,7 @@
#ifndef NEWCOIN_UINT256_H
#define NEWCOIN_UINT256_H
#include <algorithm>
#include <climits>
#include <string>
#include <vector>
@@ -39,11 +39,8 @@ public:
{
// Convert to big-endian
unsigned char *be[WIDTH];
unsigned char *bep;
unsigned int *pnp = &pn[WIDTH];
for (int i=WIDTH; i--;)
*bep++ = *--pnp;
std::reverse_copy(begin(), end(), be);
return BN_bin2bn(be, WIDTH, NULL);
}