diff --git a/bignum.h b/bignum.h index 450e809..3ad3a50 100644 --- a/bignum.h +++ b/bignum.h @@ -110,6 +110,11 @@ public: return BN_get_word(this); } + const BIGNUM *getbnp() const + { + return this; + } + int getint() const { unsigned long n = BN_get_word(this); diff --git a/key.h b/key.h index 06f88cc..8798f40 100644 --- a/key.h +++ b/key.h @@ -97,6 +97,8 @@ public: return true; } + bool SetRawECPrivKey(uint256 privKey); + CPrivKey GetPrivKey() const { unsigned int nSize = i2d_ECPrivateKey(pkey, NULL); diff --git a/rpc.cpp b/rpc.cpp index 97710ff..97aa38a 100644 --- a/rpc.cpp +++ b/rpc.cpp @@ -731,6 +731,247 @@ Value movecmd(const Array& params, bool fHelp) } +bool CKey::SetRawECPrivKey(uint256 privKey) +{ + if (!fSet) + return false; + + CAutoBN_CTX pctx; + const EC_GROUP *ecgroup = EC_KEY_get0_group(pkey); + EC_POINT *pub_key = EC_POINT_new(ecgroup); + if (!pub_key) + return false; + + CBigNum bnPrivKey(privKey); + + if (!EC_POINT_mul(ecgroup, pub_key, bnPrivKey.getbnp(), NULL, NULL, pctx) || + !EC_KEY_set_private_key(pkey, bnPrivKey.getbnp()) || + !EC_KEY_set_public_key(pkey, pub_key)) { + EC_POINT_free(pub_key); + return false; + } + + EC_POINT_free(pub_key); + return true; +} + + +// calculate 256-bit ECDSA private key from 64-bit scratch code +uint256 ScratchGetPrivKey(vector& vchPrivCode, + const char *salt) +{ + vector vchHashDataIn_1(32); + vector vchHashDataIn_2(32); + vector vchHashDataOut_1(32); + vector vchHashDataOut_2(32); + vector vchHash512DataOut(64); + + if (!salt || !strlen(salt)) + salt = "bitcoin"; + + // init hash input data to (password,salt) or (salt,password) + SHA256_CTX tc; + + // pipe1 init: (password, salt) + SHA256_Init(&tc); + SHA256_Update(&tc, &vchPrivCode[0], vchPrivCode.size()); + SHA256_Update(&tc, salt, strlen(salt)); + SHA256_Final(&vchHashDataIn_1[0], &tc); + + // pipe2 init: (salt, password) + SHA256_Init(&tc); + SHA256_Update(&tc, salt, strlen(salt)); + SHA256_Update(&tc, &vchPrivCode[0], vchPrivCode.size()); + SHA256_Final(&vchHashDataIn_2[0], &tc); + + for (int i = 0; i < 108333; i++) { + SHA512_CTX sc; + + SHA256(&vchHashDataIn_1[0], 32, &vchHashDataOut_1[0]); + SHA256(&vchHashDataIn_2[0], 32, &vchHashDataOut_2[0]); + + SHA512_Init(&sc); + SHA512_Update(&sc, &vchHashDataOut_1[0], 32); + SHA512_Update(&sc, &vchHashDataOut_2[0], 32); + SHA512_Final(&vchHash512DataOut[0], &sc); + + memcpy(&vchHashDataIn_1[0], &vchHash512DataOut[0], 32); + memcpy(&vchHashDataIn_2[0], &vchHash512DataOut[32], 32); + } + + uint256 rawPrivKey; + memcpy(rawPrivKey.begin(), &vchHashDataIn_1[0], 32); + + return rawPrivKey; +} + + +Value scratchoff(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 2 || params.size() > 4) + throw runtime_error( + "scratchoff [salt-or-null] [toaccount]\n"); + + // param 1: transaction id + uint256 txhash; + txhash.SetHex(params[0].get_str()); + + // param 2: hex-encoded private code + vector vchPrivCode = ParseHex(params[1].get_str()); + int nBits = vchPrivCode.size() * 8; + if (nBits < 64 || nBits > 1024 || (nBits & 0x7)) + throw JSONRPCError(-17, "invalid password length"); + + // param 3: optional salt + string strSalt; + if (params.size() > 2) + strSalt = params[2].get_str(); + + // param 4: optional destination account for spend + string strAccount; + if (params.size() > 3) + strAccount = AccountFromValue(params[3]); + + // calculate public, private keys from password + CKey scratchKey; + scratchKey.MakeNewKey(); + if (!scratchKey.SetRawECPrivKey(ScratchGetPrivKey(vchPrivCode, + strSalt.c_str()))) + throw JSONRPCError(-16, "Failed setting scratch-off private key"); + + // calc hash 160 from public key + vector vchPubKey = scratchKey.GetPubKey(); + uint160 pubhash = Hash160(vchPubKey); + + // read requested TX + CTxDB txdb("r"); + CTransaction tx; + if (!txdb.ReadDiskTx(txhash, tx)) + throw JSONRPCError(-18, "invalid or missing id"); + + // scan for matching pubkey hash + int64 nValue = 0; + bool matched = false; + foreach(const CTxOut& txout, tx.vout) + { + uint160 hash160 = txout.scriptPubKey.GetBitcoinAddressHash160(); + if (hash160 == pubhash) { + nValue = txout.nValue; + matched = true; + break; + } + } + if (!matched) + throw JSONRPCError(-19, "transaction not found for id/password"); + + // we know this key is ours, and matches a transaction. + + // import keypair + if (!AddKey(scratchKey)) + throw JSONRPCError(-15, "scratchoff key wallet store failed"); + + // add TX to wallet + CRITICAL_BLOCK(cs_mapWallet) + { + if (mapWallet.count(txhash) == 0) + { + CWalletTx wtx(tx); + if (!AddToWallet(wtx)) + throw JSONRPCError(-14, "scratchoff TX wallet store failed"); + } + } + + return true; +} + + +Value sendscratchoff(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 3 || params.size() > 6) + throw runtime_error( + "sendscratchoff {option1:...,option2:...} [minconf=1] [comment] [comment-to]\n" + " is a real and is rounded to the nearest 0.01"); + + string strAccount = AccountFromValue(params[0]); + int64 nAmount = AmountFromValue(params[1]); + Object objOptions = params[2].get_obj(); + int nMinDepth = 1; + if (params.size() > 3) + nMinDepth = params[3].get_int(); + + // option: bits (default: 64) + int nBits; + Value valBits = find_value(objOptions, "bits"); + if (valBits.type() == null_type) + nBits = 64; + else if (valBits.type() != int_type) + throw JSONRPCError(-13, "'bits' must be an integer"); + else { + nBits = valBits.get_int(); + if (nBits < 64 || nBits > 1024 || (nBits & 0x7)) + throw JSONRPCError(-13, "Invalid password bit size"); + } + + // option: salt (default: "bitcoin") + string strSalt; + Value valSalt = find_value(objOptions, "salt"); + if (valSalt.type() == null_type) + /* do nothing */ ; + else if (valSalt.type() != str_type) + throw JSONRPCError(-13, "'salt' must be a string"); + else + strSalt = valSalt.get_str(); + + CWalletTx wtx; + wtx.strFromAccount = strAccount; + if (params.size() > 4 && params[4].type() != null_type && !params[4].get_str().empty()) + wtx.mapValue["comment"] = params[4].get_str(); + if (params.size() > 5 && params[5].type() != null_type && !params[5].get_str().empty()) + wtx.mapValue["to"] = params[5].get_str(); + + // Generate a random private key (password) for each scratch-off card + int nBytes = nBits / 8; + vector vchPrivCode(nBytes); + RAND_bytes(&vchPrivCode[0], nBytes); + + // rebuild EC key with our 256-bit key, derived from password + CKey scratchKey; + scratchKey.MakeNewKey(); + if (!scratchKey.SetRawECPrivKey(ScratchGetPrivKey(vchPrivCode, + strSalt.c_str()))) + throw JSONRPCError(-16, "Failed setting scratch-off private key"); + + // derive script pubkey + CScript scriptPubKey; + scriptPubKey.SetBitcoinAddress(scratchKey.GetPubKey()); + + // send money to newly generated pubkey address (our scratch-off code) + CRITICAL_BLOCK(cs_mapWallet) + { + // Check funds + int64 nBalance = GetAccountBalance(strAccount, nMinDepth); + if (nAmount > nBalance) + throw JSONRPCError(-6, "Account has insufficient funds"); + + // Send + string strError = SendMoney(scriptPubKey, nAmount, wtx); + if (strError != "") + throw JSONRPCError(-4, strError); + } + + // convert scratch-off code to hexidecimal string + char pszPrivCode[(nBytes * 2) + 1]; + for (int i = 0; i < nBytes; i++) + sprintf(pszPrivCode + i*2, "%02x", vchPrivCode[i]); + + Object ret; + ret.push_back(Pair("txid", wtx.GetHash().GetHex())); + ret.push_back(Pair("password", pszPrivCode)); + + return ret; +} + + Value sendfrom(const Array& params, bool fHelp) { if (fHelp || params.size() < 3 || params.size() > 6) @@ -1406,6 +1647,8 @@ pair pCallTable[] = make_pair("validateaddress", &validateaddress), make_pair("getbalance", &getbalance), make_pair("move", &movecmd), + make_pair("sendscratchoff", &sendscratchoff), + make_pair("scratchoff", &scratchoff), make_pair("sendfrom", &sendfrom), make_pair("sendmany", &sendmany), make_pair("gettransaction", &gettransaction), @@ -2055,6 +2298,16 @@ int CommandLineRPC(int argc, char *argv[]) if (strMethod == "getbalance" && n > 1) ConvertTo(params[1]); if (strMethod == "move" && n > 2) ConvertTo(params[2]); if (strMethod == "move" && n > 3) ConvertTo(params[3]); + if (strMethod == "sendscratchoff" && n > 1) ConvertTo(params[1]); + if (strMethod == "sendscratchoff" && n > 2) ConvertTo(params[2]); + if (strMethod == "sendscratchoff" && n > 3) + { + string s = params[3].get_str(); + Value v; + if (!read_string(s, v) || v.type() != obj_type) + throw runtime_error("type mismatch"); + params[3] = v.get_obj(); + } if (strMethod == "sendfrom" && n > 2) ConvertTo(params[2]); if (strMethod == "sendfrom" && n > 3) ConvertTo(params[3]); if (strMethod == "listtransactions" && n > 1) ConvertTo(params[1]);