Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IOS-8857 [Staking] Add support for compiling with multiple signatures #49

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/TrustWalletCore/TWTransactionCompiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,16 @@ TWData* _Nonnull TWTransactionCompilerCompileWithSignatures(
enum TWCoinType coinType, TWData* _Nonnull txInputData,
const struct TWDataVector* _Nonnull signatures, const struct TWDataVector* _Nonnull publicKeys);

TW_EXPORT_STATIC_METHOD
TWData *_Nonnull TWTransactionCompilerCompileWithMultipleSignatures(
enum TWCoinType coinType, TWData *_Nonnull txInputData,
const struct TWDataVector *_Nonnull signatures, const struct TWDataVector *_Nonnull publicKeys);

TW_EXPORT_STATIC_METHOD
TWData *_Nonnull TWTransactionCompilerCompileWithSignaturesAndPubKeyType(
enum TWCoinType coinType, TWData *_Nonnull txInputData,
const struct TWDataVector *_Nonnull signatures, const struct TWDataVector *_Nonnull publicKeys,
enum TWPublicKeyType pubKeyType);

TW_EXTERN_C_END

28 changes: 28 additions & 0 deletions src/Cardano/Entry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,32 @@ void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, c
});
}

void Entry::compileWithMultipleSignatures([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector<Data>& signatures, const std::vector<PublicKey>& publicKeys, Data& dataOut) const {
auto input = Proto::SigningInput();
auto output = Proto::SigningOutput();

if (!input.ParseFromArray(txInputData.data(), (int)txInputData.size())) {
output.set_error(Common::Proto::Error_input_parse);
output.set_error_message("failed to parse input data");
dataOut = TW::data(output.SerializeAsString());
return;
}

if (signatures.empty() || publicKeys.empty()) {
output.set_error(Common::Proto::Error_invalid_params);
output.set_error_message("empty signatures or publickeys");
dataOut = TW::data(output.SerializeAsString());
return;
}

try {
auto encoded = Signer::encodeTransactionWithSignatures(input, publicKeys, signatures);
output.set_encoded(encoded.data(), encoded.size());
} catch (const std::exception& e) {
output.set_error(Common::Proto::Error_internal);
output.set_error_message(e.what());
}
dataOut = TW::data(output.SerializeAsString());
}

} // namespace TW::Cardano
2 changes: 2 additions & 0 deletions src/Cardano/Entry.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class Entry final : public CoinEntry {

Data preImageHashes(TWCoinType coin, const Data& txInputData) const;
void compile(TWCoinType coin, const Data& txInputData, const std::vector<Data>& signatures, const std::vector<PublicKey>& publicKeys, Data& dataOut) const;
void compileWithMultipleSignatures(TWCoinType coin, const Data& txInputData, const std::vector<Data>& signatures, const std::vector<PublicKey>& publicKeys, Data& dataOut) const;

};

} // namespace TW::Cardano
54 changes: 54 additions & 0 deletions src/Cardano/Signer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,60 @@ Data Signer::encodeTransactionWithSig(const Proto::SigningInput &input, const Pu
return cbor.encoded();
}

Data Signer::encodeTransactionWithSignatures(
const Proto::SigningInput &input,
const std::vector<PublicKey>& publicKeys,
const std::vector<Data>& signatures
) {
Transaction txAux;
auto buildRet = buildTx(txAux, input);
if (buildRet != Common::Proto::OK) {
throw Common::Proto::SigningError(buildRet);
}

std::vector<std::pair<Data, Data>> keySignaturesPairs = convertToKeySignaturePairs(publicKeys, signatures);

bool hasLegacyUtxos = false;
for (const auto& utxo : input.utxos()) {
if (AddressV2::isValid(utxo.address())) {
hasLegacyUtxos = true;
break;
}
}

const auto sigsCbor = cborizeSignatures(keySignaturesPairs, hasLegacyUtxos);

// Cbor-encode txAux & signatures
const auto cbor = Cbor::Encode::array({
// txaux
Cbor::Encode::fromRaw(txAux.encode()),
// signatures
sigsCbor,
// aux data
Cbor::Encode::null(),
});

return cbor.encoded();
}

std::vector<std::pair<Data, Data>> Signer::convertToKeySignaturePairs(const std::vector<PublicKey>& keys, const std::vector<Data>& signatures) {
// Check if the two vectors are of the same size
if (keys.size() != signatures.size()) {
throw std::invalid_argument("Vectors must be of the same size.");
}

std::vector<std::pair<Data, Data>> result;
result.reserve(keys.size()); // Reserve space to avoid reallocations

// Iterate over the vectors and create pairs
for (size_t i = 0; i < keys.size(); ++i) {
result.emplace_back(keys[i].bytes, signatures[i]);
}

return result;
}


Common::Proto::SigningError Signer::buildTx(Transaction& tx, const Proto::SigningInput& input) {
auto plan = Signer(input).doPlan();
return buildTransactionAux(tx, input, plan);
Expand Down
2 changes: 2 additions & 0 deletions src/Cardano/Signer.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ class Signer {
// Build encoded transaction
static Common::Proto::SigningError encodeTransaction(Data& encoded, Data& txId, const Proto::SigningInput& input, const TransactionPlan& plan, bool sizeEstimationOnly = false);
static Data encodeTransactionWithSig(const Proto::SigningInput &input, const PublicKey &publicKey, const Data &signature);
static Data encodeTransactionWithSignatures(const Proto::SigningInput &input, const std::vector<PublicKey>& publicKeys, const std::vector<Data>& signatures);
static std::vector<std::pair<Data, Data>> convertToKeySignaturePairs(const std::vector<PublicKey>& keys, const std::vector<Data>& signatures);
// Build aux transaction object, using input and plan
static Common::Proto::SigningError buildTransactionAux(Transaction& tx, const Proto::SigningInput& input, const TransactionPlan& plan);
static Common::Proto::SigningError buildTx(Transaction& tx, const Proto::SigningInput& input);
Expand Down
6 changes: 6 additions & 0 deletions src/Coin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,12 @@ void TW::anyCoinCompileWithSignatures(TWCoinType coinType, const Data& txInputDa
dispatcher->compile(coinType, txInputData, signatures, publicKeys, txOutputOut);
}

void TW::anyCoinCompileWithMultipleSignatures(TWCoinType coinType, const Data& txInputData, const std::vector<Data>& signatures, const std::vector<PublicKey>& publicKeys, Data& txOutputOut) {
auto* dispatcher = coinDispatcher(coinType);
assert(dispatcher != nullptr);
dispatcher->compileWithMultipleSignatures(coinType, txInputData, signatures, publicKeys, txOutputOut);
}

// Coin info accessors

extern const CoinInfo getCoinInfo(TWCoinType coin); // in generated CoinInfoData.cpp file
Expand Down
3 changes: 3 additions & 0 deletions src/Coin.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ Data anyCoinPreImageHashes(TWCoinType coinType, const Data& txInputData);

void anyCoinCompileWithSignatures(TWCoinType coinType, const Data& txInputData, const std::vector<Data>& signatures, const std::vector<PublicKey>& publicKeys, Data& txOutputOut);

void anyCoinCompileWithMultipleSignatures(TWCoinType coinType, const Data& txInputData, const std::vector<Data>& signatures, const std::vector<PublicKey>& publicKeys, Data& txOutputOut);


// Describes a derivation: path + optional format + optional name
struct Derivation {
TWDerivation name = TWDerivationDefault;
Expand Down
2 changes: 2 additions & 0 deletions src/CoinEntry.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ class CoinEntry {
virtual Data preImageHashes([[maybe_unused]] TWCoinType coin, [[maybe_unused]] const Data& txInputData) const { return {}; }
// Optional method for compiling a transaction with externally-supplied signatures & pubkeys.
virtual void compile([[maybe_unused]] TWCoinType coin, [[maybe_unused]] const Data& txInputData, [[maybe_unused]] const std::vector<Data>& signatures, [[maybe_unused]] const std::vector<PublicKey>& publicKeys, [[maybe_unused]] Data& dataOut) const {}
virtual void compileWithMultipleSignatures([[maybe_unused]] TWCoinType coin, [[maybe_unused]] const Data& txInputData, [[maybe_unused]] const std::vector<Data>& signatures, [[maybe_unused]] const std::vector<PublicKey>& publicKeys, [[maybe_unused]] Data& dataOut) const {}

};

// In each coin's Entry.cpp the specific types of the coin are used, this template enforces the Signer implement:
Expand Down
17 changes: 16 additions & 1 deletion src/TransactionCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,21 @@ Data TransactionCompiler::compileWithSignatures(TWCoinType coinType, const Data&
return txOutput;
}

Data TransactionCompiler::compileWithMultipleSignatures(TWCoinType coinType, const Data& txInputData, const std::vector<Data>& signatures, const std::vector<Data>& publicKeys) {
std::vector<PublicKey> pubs;
const auto publicKeyType = ::publicKeyType(coinType);
for (auto& p: publicKeys) {
if (!PublicKey::isValid(p, publicKeyType)) {
throw std::invalid_argument("Invalid public key");
}
pubs.push_back(PublicKey(p, publicKeyType));
}

Data txOutput;
anyCoinCompileWithMultipleSignatures(coinType, txInputData, signatures, pubs, txOutput);
return txOutput;
}

Data TransactionCompiler::compileWithSignaturesAndPubKeyType(TWCoinType coinType, const Data& txInputData, const std::vector<Data>& signatures, const std::vector<Data>& publicKeys, enum TWPublicKeyType pubKeyType) {
std::vector<PublicKey> pubs;
for (auto& p: publicKeys) {
Expand All @@ -36,7 +51,7 @@ Data TransactionCompiler::compileWithSignaturesAndPubKeyType(TWCoinType coinType
}
pubs.push_back(PublicKey(p, pubKeyType));
}

Data txOutput;
anyCoinCompileWithSignatures(coinType, txInputData, signatures, pubs, txOutput);
return txOutput;
Expand Down
4 changes: 4 additions & 0 deletions src/TransactionCompiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,13 @@ class TransactionCompiler {

/// Compile a complete transation with an external signature, put together from transaction input and provided public key and signature
static Data compileWithSignatures(TWCoinType coinType, const Data& txInputData, const std::vector<Data>& signatures, const std::vector<Data>& publicKeys);

/// Compile a complete transation with an external signatures, put together from transaction input and provided public keys and signatures
static Data compileWithMultipleSignatures(TWCoinType coinType, const Data& txInputData, const std::vector<Data>& signatures, const std::vector<Data>& publicKeys);

static Data compileWithSignaturesAndPubKeyType(TWCoinType coinType, const Data& txInputData, const std::vector<Data>& signatures, const std::vector<Data>& publicKeys, TWPublicKeyType pubKeyType);


};

} // namespace TW
16 changes: 16 additions & 0 deletions src/interface/TWTransactionCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ TWData *_Nonnull TWTransactionCompilerCompileWithSignatures(enum TWCoinType coin
return TWDataCreateWithBytes(result.data(), result.size());
}

TWData *_Nonnull TWTransactionCompilerCompileWithMultipleSignatures(enum TWCoinType coinType, TWData *_Nonnull txInputData, const struct TWDataVector *_Nonnull signatures, const struct TWDataVector *_Nonnull publicKeys) {
Data result;
try {
assert(txInputData != nullptr);
const Data inputData = data(TWDataBytes(txInputData), TWDataSize(txInputData));
assert(signatures != nullptr);
const auto signaturesVec = createFromTWDataVector(signatures);
assert(publicKeys != nullptr);
const auto publicKeysVec = createFromTWDataVector(publicKeys);

result = TransactionCompiler::compileWithMultipleSignatures(coinType, inputData, signaturesVec, publicKeysVec);
} catch (...) {} // return empty
return TWDataCreateWithBytes(result.data(), result.size());
}

TWData *_Nonnull TWTransactionCompilerCompileWithSignaturesAndPubKeyType(enum TWCoinType coinType, TWData *_Nonnull txInputData, const struct TWDataVector *_Nonnull signatures, const struct TWDataVector *_Nonnull publicKeys, enum TWPublicKeyType pubKeyType) {
Data result;
try {
Expand All @@ -51,3 +66,4 @@ TWData *_Nonnull TWTransactionCompilerCompileWithSignaturesAndPubKeyType(enum TW
} catch (...) {} // return empty
return TWDataCreateWithBytes(result.data(), result.size());
}

4 changes: 2 additions & 2 deletions swift/Sources/Extensions/Data+Hex.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Foundation

extension Data {
/// Initializes `Data` with a hex string representation.
init?(hexString: String) {
public init?(hexString: String) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

в этом файле также фиксы тестов

let string: String
if hexString.hasPrefix("0x") {
string = String(hexString.dropFirst(2))
Expand Down Expand Up @@ -56,7 +56,7 @@ extension Data {
}

/// Returns the hex string representation of the data.
var hexString: String {
public var hexString: String {
return map({ String(format: "%02x", $0) }).joined()
}
}
Expand Down
88 changes: 88 additions & 0 deletions swift/Tests/Blockchains/CardanoTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -334,4 +334,92 @@ class CardanoTests: XCTestCase {
let txid = output.txID
XCTAssertEqual(txid.hexString, "87ca43a36b09c0b140f0ef2b71fbdcfcf1fdc88f7aa378b861e8eed3e8974628")
}

func testSignStakingRegisterAndDelegateExternalSign() throws {
// input
let ownAddress = "addr1q8043m5heeaydnvtmmkyuhe6qv5havvhsf0d26q3jygsspxlyfpyk6yqkw0yhtyvtr0flekj84u64az82cufmqn65zdsylzk23"
let stakingAddress = Cardano.getStakingAddress(baseAddress: ownAddress)
let poolIdNufi = "7d7ac07a2f2a25b7a4db868a40720621c4939cf6aefbb9a11464f1a6"

var input = CardanoSigningInput.with {
$0.transferMessage.toAddress = ownAddress
$0.transferMessage.changeAddress = ownAddress
$0.transferMessage.amount = 4000000 // not relevant as we use MaxAmount
$0.transferMessage.useMaxAmount = true
$0.ttl = 69885081
// Register staking key, 2 ADA desposit
$0.registerStakingKey.stakingAddress = stakingAddress
$0.registerStakingKey.depositAmount = 2000000
// Delegate
$0.delegate.stakingAddress = stakingAddress
$0.delegate.poolID = Data(hexString: poolIdNufi)!
$0.delegate.depositAmount = 0
}

let utxo1 = CardanoTxInput.with {
$0.outPoint.txHash = Data(hexString: "9b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e")!
$0.outPoint.outputIndex = 0
$0.address = ownAddress
$0.amount = 4000000
}
let utxo2 = CardanoTxInput.with {
$0.outPoint.txHash = Data(hexString: "9b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e")!
$0.outPoint.outputIndex = 1
$0.address = ownAddress
$0.amount = 26651312
}
input.utxos.append(utxo1)
input.utxos.append(utxo2)

// public/private keys
let privateKeyData = Data(hexString: "089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e")!

let privateKeyBytes = Array(privateKeyData)
var stakingPrivateKeyBytes = privateKeyBytes[privateKeyBytes.count / 2 ..< privateKeyBytes.count]
stakingPrivateKeyBytes.append(contentsOf: Data(repeating: 0, count: 96))
let stakingPrivateKeyData = Data(stakingPrivateKeyBytes)

let privateKey = PrivateKey(data: privateKeyData)!
let stakingPrivateKey = PrivateKey(data: stakingPrivateKeyData)!

let publicKey = privateKey.getPublicKeyByType(pubkeyType: .ed25519Cardano)
let stakingPublicKey = stakingPrivateKey.getPublicKeyByType(pubkeyType: .ed25519Cardano)

// hash to sign
let txInputData = try input.serializedData()

let preImageHashes = TransactionCompiler.preImageHashes(coinType: .cardano, txInputData: txInputData)
let preSigningOutput = try TxCompilerPreSigningOutput(serializedData: preImageHashes)
let hash = preSigningOutput.dataHash

// signatures
let defaultSignature = privateKey.sign(digest: hash, curve: .ed25519ExtendedCardano)!
let stakingSignature = stakingPrivateKey.sign(digest: hash, curve: .ed25519ExtendedCardano)!

let signatures = DataVector()
signatures.add(data: defaultSignature)
signatures.add(data: stakingSignature)

let publicKeys = DataVector()

publicKeys.add(data: publicKey.data)
publicKeys.add(data: stakingPublicKey.data)

let serializedOutput = TransactionCompiler.compileWithMultipleSignatures(
coinType: .cardano,
txInputData: txInputData,
signatures: signatures,
publicKeys: publicKeys
)

let signingOutput = try CardanoSigningOutput(serializedData: serializedOutput)

let encoded = signingOutput.encoded
XCTAssertEqual(
encoded.hexString,
"83a500828258209b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e008258209b06de86b253549b99f6a050b61217d8824085ca5ed4eb107a5e7cce4f93802e01018182583901df58ee97ce7a46cd8bdeec4e5f3a03297eb197825ed5681191110804df22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b1a01b27ef5021a0002b03b031a042a5c99048282008200581cdf22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b83028200581cdf22424b6880b39e4bac8c58de9fe6d23d79aaf44756389d827aa09b581c7d7ac07a2f2a25b7a4db868a40720621c4939cf6aefbb9a11464f1a6a100828258206d8a0b425bd2ec9692af39b1c0cf0e51caa07a603550e22f54091e872c7df2905840677c901704be027d9a1734e8aa06f0700009476fa252baaae0de280331746a320a61456d842d948ea5c0e204fc36f3bd04c88ca7ee3d657d5a38014243c37c07825820e554163344aafc2bbefe778a6953ddce0583c2f8e0a0686929c020ca33e0693258401fa21bdc62b85ca217bf08cbacdeba2fadaf33dc09ee3af9cc25b40f24822a1a42cfbc03585cc31a370ef75aaec4d25db6edcf329e40a4e725ec8718c94f220af6"
)

XCTAssertEqual(hash.hexString, "96a781fd6481b6a7fd3926da110265e8c44b53947b81daa84da5b148825d02aa")
}
}
2 changes: 1 addition & 1 deletion swift/Tests/Blockchains/TheOpenNetworkTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class TheOpenNetworkTests: XCTestCase {
let privateKeyData = Data(hexString: "c054900a527538c1b4325688a421c0469b171c29f23a62da216e90b0df2412ee")!

let jettonTransfer = TheOpenNetworkJettonTransfer.with {
$0.jettonAmount = 500 * 1000 * 1000
$0.jettonAmount = Data(hexString: "1dcd6500")! // 500 * 1000 * 1000
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

фикс тестов

$0.toOwner = "EQAFwMs5ha8OgZ9M4hQr80z9NkE7rGxUpE1hCFndiY6JnDx8"
$0.responseAddress = "EQBaKIMq5Am2p_rfR1IFTwsNWHxBkOpLTmwUain5Fj4llTXk"
$0.forwardAmount = 1
Expand Down
Loading