Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[Bitcoin] Redeem utxos locked by uncompressed public key hash (#1433)
* support redeem utxos locked by uncompressed public key hash
* Add cpp test and minor optimization
* Add KeyPair tuple and real world test
  • Loading branch information
hewigovens committed May 19, 2021
1 parent 61f28f8 commit 377d194
Show file tree
Hide file tree
Showing 15 changed files with 207 additions and 64 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -54,3 +54,5 @@ samples/cpp/*.cmake
# built binary
samples/cpp/sample

*.xcuserdatad/

2 changes: 1 addition & 1 deletion android/build.gradle
Expand Up @@ -7,7 +7,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.3'
classpath 'com.android.tools.build:gradle:4.2.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0'
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.5'
Expand Down
2 changes: 1 addition & 1 deletion samples/osx/cocoapods/Podfile
@@ -1,4 +1,4 @@
platform :osx, '10.12'
platform :osx, '10.14'

target 'WalletCoreExample' do
use_frameworks!
Expand Down
20 changes: 10 additions & 10 deletions samples/osx/cocoapods/Podfile.lock
@@ -1,24 +1,24 @@
PODS:
- SwiftProtobuf (1.9.0)
- TrustWalletCore (2.1.1):
- TrustWalletCore/Core (= 2.1.1)
- TrustWalletCore/Core (2.1.1):
- SwiftProtobuf (1.16.0)
- TrustWalletCore (2.6.7):
- TrustWalletCore/Core (= 2.6.7)
- TrustWalletCore/Core (2.6.7):
- TrustWalletCore/Types
- TrustWalletCore/Types (2.1.1):
- TrustWalletCore/Types (2.6.7):
- SwiftProtobuf

DEPENDENCIES:
- TrustWalletCore (~> 2.1.0)
- TrustWalletCore

SPEC REPOS:
trunk:
- SwiftProtobuf
- TrustWalletCore

SPEC CHECKSUMS:
SwiftProtobuf: ecbec1be9036d15655f6b3443a1c4ea693c97932
TrustWalletCore: cd0373c69fd9bf92700783287544ce6a9aab041b
SwiftProtobuf: 4e16842b83c6fda06b10fac50d73b3f1fce8ab7b
TrustWalletCore: 1409475008901d761effd92fe6c3d6be6c83ae3a

PODFILE CHECKSUM: a34fe0b289ed6fb4db3ae7964f62fa6b21ff0c0d
PODFILE CHECKSUM: 68848e868fc9571d319a35d139d7901079a7fe36

COCOAPODS: 1.9.3
COCOAPODS: 1.10.1
19 changes: 10 additions & 9 deletions src/Bitcoin/Transaction.cpp
Expand Up @@ -9,6 +9,7 @@
#include "SigHashType.h"
#include "../BinaryCoding.h"
#include "../Hash.h"
#include "../Data.h"

#include "SignatureVersion.h"

Expand Down Expand Up @@ -46,7 +47,7 @@ Data Transaction::getPreImage(const Script& scriptCode, size_t index,
// The input being signed (replacing the scriptSig with scriptCode + amount)
// The prevout may already be contained in hashPrevout, and the nSequence
// may already be contain in hashSequence.
reinterpret_cast<const TW::Bitcoin::OutPoint&>(inputs[index].previousOutput).encode(data);
reinterpret_cast<const OutPoint&>(inputs[index].previousOutput).encode(data);
scriptCode.encode(data);

encode64LE(amount, data);
Expand All @@ -59,7 +60,7 @@ Data Transaction::getPreImage(const Script& scriptCode, size_t index,
} else if (hashTypeIsSingle(hashType) && index < outputs.size()) {
Data outputData;
outputs[index].encode(outputData);
auto hashOutputs = TW::Hash::hash(hasher, outputData);
auto hashOutputs = Hash::hash(hasher, outputData);
copy(begin(hashOutputs), end(hashOutputs), back_inserter(data));
} else {
fill_n(back_inserter(data), 32, 0);
Expand All @@ -77,10 +78,10 @@ Data Transaction::getPreImage(const Script& scriptCode, size_t index,
Data Transaction::getPrevoutHash() const {
Data data;
for (auto& input : inputs) {
auto& outpoint = reinterpret_cast<const TW::Bitcoin::OutPoint&>(input.previousOutput);
auto& outpoint = reinterpret_cast<const OutPoint&>(input.previousOutput);
outpoint.encode(data);
}
auto hash = TW::Hash::hash(hasher, data);
auto hash = Hash::hash(hasher, data);
return hash;
}

Expand All @@ -89,7 +90,7 @@ Data Transaction::getSequenceHash() const {
for (auto& input : inputs) {
encode32LE(input.sequence, data);
}
auto hash = TW::Hash::hash(hasher, data);
auto hash = Hash::hash(hasher, data);
return hash;
}

Expand All @@ -98,7 +99,7 @@ Data Transaction::getOutputsHash() const {
for (auto& output : outputs) {
output.encode(data);
}
auto hash = TW::Hash::hash(hasher, data);
auto hash = Hash::hash(hasher, data);
return hash;
}

Expand Down Expand Up @@ -163,7 +164,7 @@ Data Transaction::getSignatureHashWitnessV0(const Script& scriptCode, size_t ind
enum TWBitcoinSigHashType hashType,
uint64_t amount) const {
auto preimage = getPreImage(scriptCode, index, hashType, amount);
auto hash = TW::Hash::hash(hasher, preimage);
auto hash = Hash::hash(hasher, preimage);
return hash;
}

Expand Down Expand Up @@ -202,7 +203,7 @@ Data Transaction::getSignatureHashBase(const Script& scriptCode, size_t index,
// Sighash type
encode32LE(hashType, data);

auto hash = TW::Hash::hash(hasher, data);
auto hash = Hash::hash(hasher, data);
return hash;
}

Expand All @@ -214,7 +215,7 @@ void Transaction::serializeInput(size_t subindex, const Script& scriptCode, size
subindex = index;
}

reinterpret_cast<const TW::Bitcoin::OutPoint&>(inputs[subindex].previousOutput).encode(data);
reinterpret_cast<const OutPoint&>(inputs[subindex].previousOutput).encode(data);

// Serialize the script
if (subindex != index) {
Expand Down
70 changes: 38 additions & 32 deletions src/Bitcoin/TransactionSigner.cpp
Expand Up @@ -6,6 +6,7 @@

#include "TransactionSigner.h"

#include "KeyPair.h"
#include "TransactionInput.h"
#include "TransactionOutput.h"
#include "UnspentSelector.h"
Expand All @@ -16,6 +17,7 @@
#include "../HexCoding.h"
#include "../Zcash/Transaction.h"
#include "../Groestlcoin/Transaction.h"
#include <tuple>

using namespace TW;
using namespace TW::Bitcoin;
Expand Down Expand Up @@ -63,7 +65,7 @@ Result<Transaction, Common::Proto::SigningError> TransactionSigner<Transaction,

template <typename Transaction, typename TransactionBuilder>
Result<void, Common::Proto::SigningError> TransactionSigner<Transaction, TransactionBuilder>::sign(Script script, size_t index,
const Bitcoin::Proto::UnspentTransaction& utxo) {
const Proto::UnspentTransaction& utxo) {
assert(index < transaction.inputs.size());

Script redeemScript;
Expand Down Expand Up @@ -131,7 +133,7 @@ Result<void, Common::Proto::SigningError> TransactionSigner<Transaction, Transac

template <typename Transaction, typename TransactionBuilder>
Result<std::vector<Data>, Common::Proto::SigningError> TransactionSigner<Transaction, TransactionBuilder>::signStep(
Script script, size_t index, const Bitcoin::Proto::UnspentTransaction& utxo, uint32_t version) const {
Script script, size_t index, const Proto::UnspentTransaction& utxo, uint32_t version) const {
Transaction transactionToSign(transaction);
transactionToSign.inputs = signedInputs;
transactionToSign.outputs = transaction.outputs;
Expand All @@ -149,7 +151,7 @@ Result<std::vector<Data>, Common::Proto::SigningError> TransactionSigner<Transac
return Result<std::vector<Data>, Common::Proto::SigningError>::success({redeemScript});
}
if (script.matchPayToWitnessScriptHash(data)) {
auto scripthash = TW::Hash::ripemd(data);
auto scripthash = Hash::ripemd(data);
auto redeemScript = scriptForScriptHash(scripthash);
if (redeemScript.empty()) {
// Error: Missing redeem script
Expand All @@ -170,14 +172,13 @@ Result<std::vector<Data>, Common::Proto::SigningError> TransactionSigner<Transac
if (results.size() >= required + 1) {
break;
}
auto keyHash = TW::Hash::ripemd(TW::Hash::sha256(pubKey));
auto key = keyForPublicKeyHash(keyHash);
if (key.empty() && !estimationMode) {
auto keyHash = Hash::ripemd(Hash::sha256(pubKey));
auto pair = keyPairForPubKeyHash(keyHash);
if (!pair.has_value() && !estimationMode) {
// Error: missing key
return Result<std::vector<Data>, Common::Proto::SigningError>::failure(Common::Proto::Error_missing_private_key);
}
auto signature =
createSignature(transactionToSign, script, key, index, utxo.amount(), version);
auto signature = createSignature(transactionToSign, script, pair, index, utxo.amount(), version);
if (signature.empty()) {
// Error: Failed to sign
return Result<std::vector<Data>, Common::Proto::SigningError>::failure(Common::Proto::Error_signing);
Expand All @@ -188,53 +189,55 @@ Result<std::vector<Data>, Common::Proto::SigningError> TransactionSigner<Transac
return Result<std::vector<Data>, Common::Proto::SigningError>::success(std::move(results));
}
if (script.matchPayToPublicKey(data)) {
auto keyHash = TW::Hash::ripemd(TW::Hash::sha256(data));
auto key = keyForPublicKeyHash(keyHash);
if (key.empty() && !estimationMode) {
auto keyHash = Hash::ripemd(Hash::sha256(data));
auto pair = keyPairForPubKeyHash(keyHash);
if (!pair.has_value() && !estimationMode) {
// Error: Missing key
return Result<std::vector<Data>, Common::Proto::SigningError>::failure(Common::Proto::Error_missing_private_key);
}
auto signature =
createSignature(transactionToSign, script, key, index, utxo.amount(), version);
auto signature = createSignature(transactionToSign, script, pair, index, utxo.amount(), version);
if (signature.empty()) {
// Error: Failed to sign
return Result<std::vector<Data>, Common::Proto::SigningError>::failure(Common::Proto::Error_signing);
}
return Result<std::vector<Data>, Common::Proto::SigningError>::success({signature});
}
if (script.matchPayToPublicKeyHash(data)) {
auto key = keyForPublicKeyHash(data);
if (key.empty() && !estimationMode) {
auto pair = keyPairForPubKeyHash(data);
if (!pair.has_value() && !estimationMode) {
// Error: Missing keys
return Result<std::vector<Data>, Common::Proto::SigningError>::failure(Common::Proto::Error_missing_private_key);
}

auto signature =
createSignature(transactionToSign, script, key, index, utxo.amount(), version);
auto signature = createSignature(transactionToSign, script, pair, index, utxo.amount(), version);
if (signature.empty()) {
// Error: Failed to sign
return Result<std::vector<Data>, Common::Proto::SigningError>::failure(Common::Proto::Error_signing);
}
if (key.empty() && estimationMode) {
if (!pair.has_value() && estimationMode) {
// estimation mode, key is missing: use placeholder for public key
return Result<std::vector<Data>, Common::Proto::SigningError>::success({signature, Data(PublicKey::secp256k1Size)});
}
auto pubkey = PrivateKey(key).getPublicKey(TWPublicKeyTypeSECP256k1);
auto pubkey = std::get<1>(pair.value());
return Result<std::vector<Data>, Common::Proto::SigningError>::success({signature, pubkey.bytes});
}
// Error: Invalid output script
return Result<std::vector<Data>, Common::Proto::SigningError>::failure(Common::Proto::Error_script_output);
}

template <typename Transaction, typename TransactionBuilder>
Data TransactionSigner<Transaction, TransactionBuilder>::createSignature(const Transaction& transaction,
const Script& script, const Data& key,
size_t index, Amount amount,
uint32_t version) const {
Data TransactionSigner<Transaction, TransactionBuilder>::createSignature(
const Transaction& transaction,
const Script& script,
const std::optional<KeyPair>& pair,
size_t index,
Amount amount,
uint32_t version
) const {
if (estimationMode) {
// Don't sign, only estimate signature size. It is 71-72 bytes. Return placeholder.
return Data(72);
}
auto key = std::get<0>(pair.value());
Data sighash = transaction.getSignatureHash(script, index, static_cast<TWBitcoinSigHashType>(input.hash_type()), amount,
static_cast<SignatureVersion>(version));
auto pk = PrivateKey(key);
Expand Down Expand Up @@ -271,12 +274,15 @@ Data TransactionSigner<Transaction, TransactionBuilder>::pushAll(const std::vect
}

template <typename Transaction, typename TransactionBuilder>
Data TransactionSigner<Transaction, TransactionBuilder>::keyForPublicKeyHash(const Data& hash) const {
std::optional<KeyPair> TransactionSigner<Transaction, TransactionBuilder>::keyPairForPubKeyHash(const Data& hash) const {
for (auto& key : input.private_key()) {
auto publicKey = PrivateKey(key).getPublicKey(TWPublicKeyTypeSECP256k1);
auto keyHash = TW::Hash::ripemd(TW::Hash::sha256(publicKey.bytes));
if (keyHash == hash) {
return Data(key.begin(), key.end());
auto privKey = PrivateKey(key);
auto pubKeyExtended = privKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended);
auto pubKey = pubKeyExtended.compressed();
if (Hash::sha256ripemd(pubKey.bytes.data(), pubKey.bytes.size()) == hash) {
return std::make_tuple(privKey, pubKey);
} else if (Hash::sha256ripemd(pubKeyExtended.bytes.data(), pubKeyExtended.bytes.size()) == hash) {
return std::make_tuple(privKey, pubKeyExtended);
}
}
return {};
Expand All @@ -294,6 +300,6 @@ Data TransactionSigner<Transaction, TransactionBuilder>::scriptForScriptHash(con
}

// Explicitly instantiate a Signers for compatible transactions.
template class TW::Bitcoin::TransactionSigner<Bitcoin::Transaction, Bitcoin::TransactionBuilder>;
template class TW::Bitcoin::TransactionSigner<Zcash::Transaction, Zcash::TransactionBuilder>;
template class TW::Bitcoin::TransactionSigner<Groestlcoin::Transaction, Bitcoin::TransactionBuilder>;
template class Bitcoin::TransactionSigner<Bitcoin::Transaction, TransactionBuilder>;
template class Bitcoin::TransactionSigner<Zcash::Transaction, Zcash::TransactionBuilder>;
template class Bitcoin::TransactionSigner<Groestlcoin::Transaction, TransactionBuilder>;
6 changes: 4 additions & 2 deletions src/Bitcoin/TransactionSigner.h
Expand Up @@ -14,6 +14,7 @@
#include "../Groestlcoin/Transaction.h"
#include "../Hash.h"
#include "../PrivateKey.h"
#include "../KeyPair.h"
#include "../Result.h"
#include "../Zcash/Transaction.h"
#include "../Zcash/TransactionBuilder.h"
Expand All @@ -22,6 +23,7 @@
#include <memory>
#include <string>
#include <vector>
#include <optional>

namespace TW::Bitcoin {

Expand Down Expand Up @@ -75,11 +77,11 @@ class TransactionSigner {
Result<void, Common::Proto::SigningError> sign(Script script, size_t index, const Proto::UnspentTransaction& utxo);
Result<std::vector<Data>, Common::Proto::SigningError> signStep(Script script, size_t index,
const Proto::UnspentTransaction& utxo, uint32_t version) const;
Data createSignature(const Transaction& transaction, const Script& script, const Data& key,
Data createSignature(const Transaction& transaction, const Script& script, const std::optional<KeyPair>&,
size_t index, Amount amount, uint32_t version) const;

/// Returns the private key for the given public key hash.
Data keyForPublicKeyHash(const Data& hash) const;
std::optional<KeyPair> keyPairForPubKeyHash(const Data& hash) const;

/// Returns the redeem script for the given script hash.
Data scriptForScriptHash(const Data& hash) const;
Expand Down
17 changes: 17 additions & 0 deletions src/KeyPair.h
@@ -0,0 +1,17 @@
// Copyright © 2017-2021 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

#pragma once

#include <tuple>
#include "PrivateKey.h"
#include "PublicKey.h"

namespace TW {

typedef std::tuple<PrivateKey, PublicKey> KeyPair;

} // namespace
6 changes: 6 additions & 0 deletions swift/Sources/Extensions/Data+Hex.swift
Expand Up @@ -46,6 +46,12 @@ extension Data {
return UInt8(letter, radix: 16)
}

/// Reverses and parses hex string as `Data`
public static func reverse(hexString: String) -> Data {
guard let data = Data(hexString: hexString) else { return Data() }
return Data(data.reversed())
}

/// Returns the hex string representation of the data.
public var hexString: String {
return map({ String(format: "%02x", $0) }).joined()
Expand Down

0 comments on commit 377d194

Please sign in to comment.