trustwallet / wallet-core

Cross-platform, cross-blockchain wallet library.
https://developer.trustwallet.com/wallet-core
Apache License 2.0
2.82k stars 1.58k forks source link

Implement signJSON method for all supported coins #1340

Closed gituser closed 3 years ago

gituser commented 3 years ago

Is your feature request related to a problem? Please describe. I've been using wallet-core in my project which basically consists of small Go daemon which serves requests by JSON-RPC for:

Many coins right now are not supporting signJSON method, these the only ones that are:

build-bionic:~/trustwallet/wallet-core$ grep -r -i 'return Signer::signJson' src/|cut -f 2 -d /
Nano
Cosmos
Filecoin
Binance
Polkadot (*)
Ethereum
Tezos
Elrond

So I have to implement signing by forming input inside the Go program which is not very handy and also gives much more complication to my small Go daemon and gives me more room to make mistakes.

Supporting signJSON will give more freedom for forming transactions, though you need to know exact format (which of course can be figured out if you try very hard at reading sources and understanding certain protobuf structures).

Describe the solution you'd like add Signer::signJSON to all supported coins in wallet-core

Describe alternatives you've considered I've been trying for days to use Polkadot signer and finally found out that it's possible to use json payload.

I've patched wallet-core with this patch:

diff --git a/src/Polkadot/Entry.cpp b/src/Polkadot/Entry.cpp
index 4db8a184..b865da94 100644
--- a/src/Polkadot/Entry.cpp
+++ b/src/Polkadot/Entry.cpp
@@ -22,6 +22,10 @@ string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byt
     return Address(publicKey).string();
 }

+string Entry::signJSON(TWCoinType coin, const std::string& json, const Data& key) const {
+    return Signer::signJSON(json, key);
+}
+
 void Entry::sign(TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const {
     signTemplate<Signer, Proto::SigningInput>(dataIn, dataOut);
 }
diff --git a/src/Polkadot/Entry.h b/src/Polkadot/Entry.h
index e0932bba..fed3c81c 100644
--- a/src/Polkadot/Entry.h
+++ b/src/Polkadot/Entry.h
@@ -17,6 +17,8 @@ public:
     virtual const std::vector<TWCoinType> coinTypes() const { return {TWCoinTypePolkadot}; }
     virtual bool validateAddress(TWCoinType coin, const std::string& address, TW::byte p2pkh, TW::byte p2sh, const char* hrp) const;    
     virtual std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TW::byte p2pkh, const char* hrp) const;
+    virtual bool supportsJSONSigning() const { return true; }
+    virtual std::string signJSON(TWCoinType coin, const std::string& json, const Data& key) const;
     virtual void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const;
 };

diff --git a/src/Polkadot/Signer.cpp b/src/Polkadot/Signer.cpp
index c7f708ad..411b7b5c 100644
--- a/src/Polkadot/Signer.cpp
+++ b/src/Polkadot/Signer.cpp
@@ -8,6 +8,8 @@
 #include "Extrinsic.h"
 #include "../Hash.h"
 #include "../PrivateKey.h"
+#include "HexCoding.h"
+#include <google/protobuf/util/json_util.h>

 using namespace TW;
 using namespace TW::Polkadot;
@@ -15,18 +17,32 @@ using namespace TW::Polkadot;
 static constexpr size_t hashTreshold = 256;

 Proto::SigningOutput Signer::sign(const Proto::SigningInput &input) noexcept {
-    auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end()));
-    auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519);
-    auto extrinsic = Extrinsic(input);
-    auto payload = extrinsic.encodePayload();
-    // check if need to hash
-    if (payload.size() > hashTreshold) {
-        payload = Hash::blake2b(payload, 32);
+    try {
+      auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end()));
+      auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519);
+      auto extrinsic = Extrinsic(input);
+      auto payload = extrinsic.encodePayload();
+      // check if need to hash
+      if (payload.size() > hashTreshold) {
+          payload = Hash::blake2b(payload, 32);
+      }
+      auto signature = privateKey.sign(payload, TWCurveED25519);
+      auto encoded = extrinsic.encodeSignature(publicKey, signature);
+
+      auto protoOutput = Proto::SigningOutput();
+      protoOutput.set_encoded(encoded.data(), encoded.size());
+      return protoOutput;
+    } catch (std::exception&) {
+        return Proto::SigningOutput();
     }
-    auto signature = privateKey.sign(payload, TWCurveED25519);
-    auto encoded = extrinsic.encodeSignature(publicKey, signature);

-    auto protoOutput = Proto::SigningOutput();
-    protoOutput.set_encoded(encoded.data(), encoded.size());
-    return protoOutput;
 }
+
+std::string Signer::signJSON(const std::string& json, const Data& key) {
+    auto input = Proto::SigningInput();
+    google::protobuf::util::JsonStringToMessage(json, &input);
+    input.set_private_key(key.data(), key.size());
+    auto output = Signer::sign(input);
+    return hex(output.encoded());
+}
+
diff --git a/src/Polkadot/Signer.h b/src/Polkadot/Signer.h
index 4b790bd8..435debc1 100644
--- a/src/Polkadot/Signer.h
+++ b/src/Polkadot/Signer.h
@@ -20,6 +20,10 @@ public:

     /// Signs a Proto::SigningInput transaction
     static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept;
+
+    /// Signs a json Proto::SigningInput with private key
+    static std::string signJSON(const std::string& json, const Data& key);
+
 };

 } // namespace TW::Polkadot

and after many tries of forming correct JSON payload I was able to make a signing of Polkadot transaction through signJSON method with json payload like this:

{"era":{"blockNumber":"3541050","period":"64"},"nonce":"3","transactionVersion":"5","network":"0","specVersion":"26","blockHash":"XSFDu4CGJtY61+HNpw+oaXBZ1nCpkugs1ED7uV6kA1E=","genesisHash":"kbFxuxWOLThI+iOp8cJRgvuOIDE7LB60khnaenDOkMM=","balanceCall":{"transfer":{"toAddress":"13ZLCqJNPsRZYEbwjtZZFpWt9GyFzg5WahXCVWKpWdUJqrQ5","value":"dzWUAA=="}}}

Unfortunately, I'm very bad at C++, so it's hard for me to give you the proper pull request with proper tests etc, so I'm posting this issue as a feature request with additional details.

Thank you!

gituser commented 3 years ago

Related PR - https://github.com/trustwallet/wallet-core/pull/890

hewigovens commented 3 years ago

the best way is to use go protobuf library to create type safe type models, see https://github.com/trustwallet/wallet-core/tree/master/samples/go/protos/bitcoin, json format is easy to mess up

gituser commented 3 years ago

@hewigovens I tried this approach and find it very hard to do.

JSON gives you more flexibility in terms of signing, though some things regarding JSON signing should be documented more, e.g. that for bytes data you need to base64 it certain way prior to sending as a JSON payload.

Let's say you want to implement different Polkadot operations:

You'd need to add code for each operation instead of controlling simple JSON payload which you pass to Polkadot signJSON method.