BIP143 tx signing

This commit is contained in:
gladcow 2020-02-06 08:51:33 +03:00
parent 79cc08c8bf
commit dcd6febb1d
3 changed files with 128 additions and 34 deletions

View file

@ -7,6 +7,7 @@
namespace graphene { namespace peerplays_sidechain {
static const unsigned char OP_0 = 0x00;
static const unsigned char OP_IF = 0x63;
static const unsigned char OP_ENDIF = 0x68;
static const unsigned char OP_SWAP = 0x7c;
@ -253,7 +254,11 @@ void btc_tx::to_bytes(bytes& stream) const
if(hasWitness)
{
for(const auto& in: vin)
str.writedata(in.scriptWitness);
{
str.write_compact_int(in.scriptWitness.size());
for(const auto& stack_item: in.scriptWitness)
str.writedata(stack_item);
}
}
str.writedata32(nLockTime);
}
@ -301,10 +306,14 @@ size_t btc_tx::fill_from_bytes(const bytes& data, size_t pos)
ds.set_pos(pos);
}
if (hasWitness) {
/* The witness flag is present, and we support witnesses. */
for (auto& in: vin)
ds.readdata(in.scriptWitness);
/* The witness flag is present, and we support witnesses. */
for (auto& in: vin)
{
unsigned int size = ds.read_compact_int();
in.scriptWitness.resize(size);
for(auto& stack_item: in.scriptWitness)
ds.readdata(stack_item);
}
}
nLockTime = ds.readdata32();
return ds.current_pos();
@ -340,7 +349,7 @@ bytes generate_redeem_script(std::vector<std::pair<fc::ecc::public_key, int> > k
return result;
}
std::string p2sh_address_from_redeem_script(const bytes& script, bitcoin_network network)
std::string p2wsh_address_from_redeem_script(const bytes& script, bitcoin_network network)
{
bytes data;
// add version byte
@ -371,45 +380,125 @@ std::string p2sh_address_from_redeem_script(const bytes& script, bitcoin_network
bytes lock_script_for_redeem_script(const bytes &script)
{
bytes result;
result.push_back(OP_HASH160);
fc::ripemd160 h = fc::ripemd160::hash(reinterpret_cast<const char*>(&script[0]), script.size());
result.push_back(OP_0);
fc::sha256 h = fc::sha256::hash(fc::sha256::hash(reinterpret_cast<const char*>(&script[0]), script.size()));
bytes shash(h.data(), h.data() + h.data_size());
add_data_to_script(result, shash);
result.push_back(OP_EQUAL);
return result;
}
bytes signature_for_raw_transaction(const bytes& unsigned_tx, const fc::ecc::private_key& priv_key)
bytes hash_prevouts(const btc_tx& unsigned_tx)
{
fc::sha256 digest = fc::sha256::hash(fc::sha256::hash(reinterpret_cast<const char*>(&unsigned_tx[0]), unsigned_tx.size()));
fc::ecc::compact_signature res = priv_key.sign_compact(digest);
return bytes(res.begin(), res.begin() + res.size());
fc::sha256::encoder hasher;
for(const auto& in: unsigned_tx.vin)
{
bytes data;
in.to_bytes(data);
hasher.write(reinterpret_cast<const char*>(&data[0]), data.size());
}
fc::sha256 res = fc::sha256::hash(hasher.result());
return bytes(res.data(), res.data() + res.data_size());
}
bytes hash_sequence(const btc_tx& unsigned_tx)
{
fc::sha256::encoder hasher;
for(const auto& in: unsigned_tx.vin)
{
hasher.write(reinterpret_cast<const char*>(&in.nSequence), sizeof(in.nSequence));
}
fc::sha256 res = fc::sha256::hash(hasher.result());
return bytes(res.data(), res.data() + res.data_size());
}
bytes hash_outputs(const btc_tx& unsigned_tx)
{
fc::sha256::encoder hasher;
for(const auto& out: unsigned_tx.vout)
{
bytes data;
out.to_bytes(data);
hasher.write(reinterpret_cast<const char*>(&data[0]), data.size());
}
fc::sha256 res = fc::sha256::hash(hasher.result());
return bytes(res.data(), res.data() + res.data_size());
}
std::vector<bytes> signature_for_raw_transaction(const bytes& unsigned_tx,
std::vector<uint64_t> in_amounts,
const bytes& redeem_script,
const fc::ecc::private_key& priv_key)
{
btc_tx tx;
tx.fill_from_bytes(unsigned_tx);
FC_ASSERT(tx.vin.size() == in_amounts.size(), "Incorrect input amounts data");
std::vector<bytes> results;
auto cur_amount = in_amounts.begin();
// pre-calc reused values
bytes hashPrevouts = hash_prevouts(tx);
bytes hashSequence = hash_sequence(tx);
bytes hashOutputs = hash_outputs(tx);
// calc digest for every input according to BIP143
// implement SIGHASH_ALL scheme
for(const auto& in: tx.vin)
{
fc::sha256::encoder hasher;
hasher.write(reinterpret_cast<const char*>(&tx.nVersion), sizeof(tx.nVersion));
hasher.write(reinterpret_cast<const char*>(&hashPrevouts[0]), hashPrevouts.size());
hasher.write(reinterpret_cast<const char*>(&hashSequence[0]), hashSequence.size());
bytes data;
in.prevout.to_bytes(data);
hasher.write(reinterpret_cast<const char*>(&data[0]), data.size());
bytes serializedScript;
WriteBytesStream stream(serializedScript);
stream.writedata(redeem_script);
hasher.write(reinterpret_cast<const char*>(&serializedScript[0]), serializedScript.size());
uint64_t amount = *cur_amount++;
hasher.write(reinterpret_cast<const char*>(&amount), sizeof(amount));
hasher.write(reinterpret_cast<const char*>(&in.nSequence), sizeof(in.nSequence));
hasher.write(reinterpret_cast<const char*>(&hashOutputs[0]), hashOutputs.size());
hasher.write(reinterpret_cast<const char*>(&tx.nLockTime), sizeof(tx.nLockTime));
// add sigtype SIGHASH_ALL
hasher.put(1);
fc::sha256 digest = fc::sha256::hash(hasher.result());
fc::ecc::compact_signature res = priv_key.sign_compact(digest);
results.push_back(bytes(res.begin(), res.begin() + res.size()));
}
return results;
}
bytes sign_pw_transfer_transaction(const bytes &unsigned_tx, const bytes& redeem_script, const std::vector<fc::optional<fc::ecc::private_key> > &priv_keys)
bytes sign_pw_transfer_transaction(const bytes &unsigned_tx, std::vector<uint64_t> in_amounts, const bytes& redeem_script, const std::vector<fc::optional<fc::ecc::private_key> > &priv_keys)
{
btc_tx tx;
tx.fill_from_bytes(unsigned_tx);
fc::ecc::compact_signature dummy_sig;
bytes dummy_data(dummy_sig.begin(), dummy_sig.begin() + dummy_sig.size());
for(auto key: priv_keys)
{
if(key)
{
std::vector<bytes> signatures = signature_for_raw_transaction(unsigned_tx, in_amounts, redeem_script, *key);
FC_ASSERT(signatures.size() == tx.vin.size(), "Invaid signatures number");
for(unsigned int i = 0; i < tx.vin.size(); i++)
tx.vin[i].scriptWitness.push_back(signatures[i]);
}
else
{
for(unsigned int i = 0; i < tx.vin.size(); i++)
tx.vin[i].scriptWitness.push_back(dummy_data);
}
}
for(auto& in: tx.vin)
{
WriteBytesStream script(in.scriptSig);
for(auto key: priv_keys)
{
if(key)
{
bytes signature = signature_for_raw_transaction(unsigned_tx, *key);
script.writedata(signature);
}
else
{
script.writedata(dummy_data);
}
}
script.writedata(redeem_script);
in.scriptWitness.push_back(redeem_script);
}
tx.hasWitness = true;
bytes ret;
tx.to_bytes(ret);
return ret;

View file

@ -11,13 +11,16 @@ enum bitcoin_network {
};
bytes generate_redeem_script(std::vector<std::pair<fc::ecc::public_key, int> > key_data);
std::string p2sh_address_from_redeem_script(const bytes& script, bitcoin_network network = mainnet);
std::string p2wsh_address_from_redeem_script(const bytes& script, bitcoin_network network = mainnet);
bytes lock_script_for_redeem_script(const bytes& script);
/*
* unsigned_tx - tx, all inputs of which are spends of the PW P2SH address
* returns signed transaction
*/
bytes sign_pw_transfer_transaction(const bytes& unsigned_tx, const bytes& redeem_script, const std::vector<fc::optional<fc::ecc::private_key>>& priv_keys);
bytes sign_pw_transfer_transaction(const bytes& unsigned_tx,
std::vector<uint64_t> in_amounts,
const bytes& redeem_script,
const std::vector<fc::optional<fc::ecc::private_key>>& priv_keys);
struct btc_outpoint
{
@ -33,7 +36,7 @@ struct btc_in
btc_outpoint prevout;
bytes scriptSig;
uint32_t nSequence;
bytes scriptWitness;
std::vector<bytes> scriptWitness;
void to_bytes(bytes& stream) const;
size_t fill_from_bytes(const bytes& data, size_t pos = 0);

View file

@ -46,7 +46,7 @@ BOOST_AUTO_TEST_CASE(pw_transfer)
// redeem script for old PW
bytes redeem_old =generate_redeem_script(weights_old);
// Old PW address
std::string old_pw = p2sh_address_from_redeem_script(redeem_old, bitcoin_network::testnet);
std::string old_pw = p2wsh_address_from_redeem_script(redeem_old, bitcoin_network::testnet);
// This address was filled with testnet transaction 8d8a466f6c829175a8bb747860828b59e7774be0bbf79ffdc70d5e75348180ca
BOOST_REQUIRE(old_pw == "2NGLS3x8Vk3vN18672YmSnpASm7FxYcoWu6");
@ -68,7 +68,7 @@ BOOST_AUTO_TEST_CASE(pw_transfer)
// redeem script for new PW
bytes redeem_new =generate_redeem_script(weights_new);
// New PW address
std::string new_pw = p2sh_address_from_redeem_script(redeem_new, bitcoin_network::testnet);
std::string new_pw = p2wsh_address_from_redeem_script(redeem_new, bitcoin_network::testnet);
BOOST_REQUIRE(new_pw == "2MyzbFRwNqj1Y4Q4oWELhDwz5DCHkTndE1S");
@ -100,9 +100,11 @@ BOOST_AUTO_TEST_CASE(pw_transfer)
tx.vout.push_back(output);
bytes unsigned_tx;
tx.to_bytes(unsigned_tx);
ilog(fc::to_hex(reinterpret_cast<char*>(&unsigned_tx[0]), unsigned_tx.size()));
std::vector<uint64_t> in_amounts({20000});
std::vector<fc::optional<fc::ecc::private_key>> keys_to_sign;
for(auto key: priv_old)
keys_to_sign.push_back(fc::optional<fc::ecc::private_key>(key));
bytes signed_tx =sign_pw_transfer_transaction(unsigned_tx, redeem_old, keys_to_sign);
bytes signed_tx =sign_pw_transfer_transaction(unsigned_tx, in_amounts, redeem_old, keys_to_sign);
ilog(fc::to_hex(reinterpret_cast<char*>(&signed_tx[0]), signed_tx.size()));
}