HOWTO Verify Messages Between JavaScript and Java with a Graphene KeyPair

in steemdev •  9 months ago

I'm working on a project where the authentication system only requires browser-side access to a user's STEEM Posting Key (similarly to how STEEM, DTube, and DSound interact with the blockchain).

The main difference, however, is that I also need to verify messages (ie. user-specific requests) between a client and server that are not directly verified by the STEEM blockchain. For example, how can I confirm on my back-end server (written in Java) that a particular STEEM user is really who they say they are in the browser?

When you're logged into STEEMIT or DTube, only the front-end really knows you're logged in. And that's just fine, because any transactions such as upvotes that require validation will be verified and encoded by one of the STEEM witnesses into the next block. And if the transaction is accepted, the latest "state" request will reflect any changes effected by all newly accepted transactions collectively.

In my case, however, I need to extend that functionality off-chain, and as such, it would be up to my Java server to validate and verify that the request is a current, non-duplicate "transaction" being requested by a true holder of a valid private posting key vis-a-vis a particular STEEM userID.

At first, I thought this would be a relatively easy problem to solve, because every graphene library that can sign transactions for the blockchain has the required code to perform this signing function. However, most libraries tend to obfuscate their "inner workings". For example, most users of the steemjs library are interested in methods such as:

steem.broadcast.vote(wif, voter, author, permlink, weight, function(err, result) {
console.log(err, result);
});

How that's actually accomplished is a bit less obvious, and the methods used are generally hidden from the library's end users as not to confuse them further. And while I was able to figure out the sign/verify process relatively easily in either javascript or Java, I found myself having an awfully tough time doing it in a way that was inter-operable between the two!

Making it more confusing is that the various code examples I came across were slightly different depending on which chain was being targeted. For example, some libraries seemed to use the bitcoin version, which would perform Sha256Hash.twiceOf(). For graphene chains, the payload is only Sha256Hashed once. Also, while many libraries are designed to sign transactions, they don't focus so much on actually verifying signatures, since that task is normally left up to the witnesses or miners before they're encoded to the blockchain.

And, despite all my googling around and asking others if they had attempted any "off-chain" signing of messages in a compatible way, while some may have attempted it at some point or another, none had a working solution that could perform this seemingly simple extension of a widely performed blockchain operation.

What to do, what to do...?! 😩

Well, as I've often done in the past, if you can't find it readily available, dig, dig, dig, test, test, test, and then dig some more, 😱 pull out a few more hairs from your head 😱, and then test again...! 😩

As such, I traced through the finer details of how these libraries were performing their sign and verify operations. In this case, the main libraries I'm using are steemjs on the javascript side, and steemJ on the Java side.

For those who understand what's involved in a crypto signature, what made this even trickier to solve (which I still don't quite get) is that the signature objects on each side (javascript versus java) would return different r and s combinations, although I later discovered that both were still validating correctly! If anyone has more insight into why this is, please feel free to share in the comments...

To spare you more of my "rambling", the good news is that I finally came up with a solution, which I then rolled up into a relatively simple Java class for anyone to use, called GrapheneUtils.java, which I've now released in my github crypto-playpen repo.

Understanding the Code

GrapheneUtils.java contains both the Java class, and the javascript functions and examples (wrapped in the comments). Also, in order to access the required methods in steemJS, I had to expose a few additional classes, such as the ecc and buffer classes. That updated version of the steemJS library is also available in my github crypto-playpen repo.

In order for the cross-compatible sign/verify to work, I basically made sure that the messages were being processed in the same way, and the signatures were in a compatible format that both sides would understand. Each signature consists of 32-byte R and S integer components, which can be directly serialized to both java's BigInteger type, and javascript's "bigi" BigInteger object. An additional header byte is included for compatibility with other implementations, though when deserializing to javascript, the first byte has to be 32 (base10) to indicate the number of BigInteger bytes. The final format is 1 x 32 x 32, or 1 header byte, 32-bytes for R, and another 32-bytes for S, for a total of 65-bytes. This byte buffer is then converted to its base64 representation, and we're set to go!

In javascript, the functions to achieve this (using my custom-rolled steem.min.js) are as follows:

For the sake of clarity, the above signing call is equivalent to:

const sigObj = steem.ecc.Signature.signBufferSha256(steem.ecc.hash.sha256(msg), privateWif);

In Java, the equivalent functions to encode and decode the same content are as follows:

I've also included a few additional support functions that convert keys between their bytecode and textual (base58) representations. It turns out this was also less intuitive than you would at first expect, and I also had to dig around various libraries and examples to find the correct way to do that as well.

And once you've set this all up, you too will now be empowered with the ability to verify the authenticity of a signed message versus a particular STEEM or other graphene user!

Here's an example from the javascript side:

One last thing though, if you do plan to use this technique to verify messages, you'll also want to include a token and/or a timestamp as well. For example, what if someone intercepts one of the signed packets shown above? Anyone with access to the packet could potentially send that packet again and the server will verify it as valid.

However, if you first send the user a new token before each signed request, that would force the user to generate a freshly signed packet with the correct signing key upon each new login (or any other requested operation, for that matter). If the token encoded into the message doesn't match the one expected by the server, the transaction is simply rejected as invalid.

{"sig":"ABC/7qvUhfvwcGr/cLjR6PXM6kP/olu53FBct6JNgQPfYs2dgBJQS5hFjyvKBcGXFzUsLOTkLVdTnWsVgYhUJIs=","message":["login",{"steem_id":"someSteemUser","token":"7prta-JSla0"}]}

Taking this a step further, you don't even have to include the token in the message. You can simply add it to the signing and verification calls as such:

Wrapping it up...

I'm quite pleased I managed to pull this together, since it opens the doors to using STEEM (and other graphene-based blockchains) as an off-chain authentication system for trusted hybrid blockchain projects where the keys are still maintained locally, but an app can still securely hook into the system to extend functionality beyond the current capabilities of a particular chain.

For example, several services now require a user to link their STEEM userIDs to another account by sending a small transfer of 0.001 SBD or STEEM. Using this technique, the user could simply sign a message along with a token issued by the server, and the server can then validate the signature versus the user's public key pulled directly from the STEEM blockchain.

And finally, for those who've made it this far, but aren't really into blockchain programming, kudos to ya for lasting this long! I can only hope this post provides a bit more insight and understanding into the revolutionary tech that's now taking us... Beyond Bitcoin! 😀

As always, I appreciate your upvote, your follow and all your comments!

GitHub Link: GrapheneUtils.java on github
GitHub Repo: crypto-playpen repo on github

GitHub Repo: steemJ: Steem api wrapper for Java, by @dez1337
GitHub Repo: crypto-core: Swiss Army knife for Blockchain related projects, also by @dez1337

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

this is a waaaaaaaaaaaaaaaaaaay delayed comment...

But with regards to "the signature objects on each side (javascript versus java) would return different r and s combinations, although I later discovered that both were still validating correctly! If anyone has more insight into why this is, please feel free to share in the comments..."

It's because ECDSA signatures are generated using a secret number k which is NEVER the same (leading to different r and s). See http://nlitsme.github.io/posts/2014-06-19-ecdsa-explanation/

as an off-chain authentication system for trusted hybrid blockchain projects

This sounds very very exciting. I have been asked to do a certain sort of double blinded verification (almost AAA) and this seems to the approach to follow.

though you made me remember DIAMETER and adventures with RADIUS auth sigh!

·

yeah, you're right, there are indeed quite a few similarities there... lol

what if someone intercepts one of the signed packets shown above?

Yea but what are the odds of that happening if dont' mind asking? all these come out a bit difficult but it's def worth the time to learn about it.

  • Thanks in advance!* 🙏
·

there are many cases actually, for example if the data is not encrypted (ie. http versus https), or if packets are sent via email. and while it may be less likely to happen on an encrypted site, why risk leaving such a large potential attack vector open?

As such, the graphene blockchain takes it to a whole other level than even what I've described. If you'd like a better understanding of how STEEM validates transactions, @xeroc does a great job in explaining it in his post, "Steem transaction signing in a nutshell":

We notice that our operation is now part of the transaction (as part of the operations array) and that we now have a field for our signatures and an expiration. The expiration allows for transactions to expire if they are not included into a block by that time. Usually that date is about 30 seconds in the future.

Let's discuss the ref_block_* parameters a little: The ref_block_num indicates a particular block in the past by referring to the block number which has this number as the last two bytes. The ref_block_prefix on the other hand is obtain from the block id of that particular reference block.

The purpose of these two parameters is to prevent replay attacks in the case of a fork. Once two chains have forked, the two parameters identify two different blocks. Applying a signed transaction of one chain at another chain will invalidate the signature.

Hey.)
I will be glad to meet you
my profile pays rewards from 50-70% for app)
subscribe and I will be glad to communicate with everyone

your post is all the great post sir.I like your post.Thank you very much.

very interesting and I am very interested when reading it .. Thank you

Sounds like you're preparing to build with Smart Media Tokens.

Congratulations! This post has been upvoted from the communal account, @minnowsupport, by alexpmorris from the Minnow Support Project. It's a witness project run by aggroed, ausbitbank, teamsteem, theprophet0, someguy123, neoxian, followbtcnews, and netuoso. The goal is to help Steemit grow by supporting Minnows. Please find us at the Peace, Abundance, and Liberty Network (PALnet) Discord Channel. It's a completely public and open space to all members of the Steemit community who voluntarily choose to be there.

If you would like to delegate to the Minnow Support Project you can do so by clicking on the following links: 50SP, 100SP, 250SP, 500SP, 1000SP, 5000SP.
Be sure to leave at least 50SP undelegated on your account.

It's really informative post...thanks for sharing the post...carry on
..

The post is very good, very inspiring and information for us the steemians,,

Hello alex, I still have a hard time understanding this and I have a question that may not be related to the post, but I know that you could guide me, I have seen how with some users and the amount of their posts are crossed out and say "payment refused" , Why is this happening?

·

Any user can choose to decline payout on a post. The reasons vary, sometimes it's simply a community update. I once declined payout on a post that highlighted the charitable deeds of a friend, since it didn't seem appropriate to profit from it. Hope this helps clarify it a bit for you. :)

This post may help you better understand more details of the post payout process as well...

Link: The Ultimate Guide To Steemit Payouts

·
·

gracias por aclarar mi duda.

Hahaha... I don't understand anything about it. But maybe a little? I am using both, HTML and MarkDown. This is only I can say... LOL

Informative post.Thanks for sharing it

Doing transactions outside the steem is always risky because it involves your key posting at least...

·

as an off-chain authentication system for trusted hybrid blockchain projects where the keys are still maintained locally

that's why I mentioned for trusted projects. and, of course, there are various levels of that as well. For example, it's a lot less risky to share your posting key versus an active key, which is what many services such a DTube, DLive, and DSound require.

Nice to put a voice to the face!! Great job on WhaleShares! Tech can be fun! (Techy stuff is the new sexy!!)

Looks very interesting. Also, looks very technical and complicated. Don't understand it very much but was in post your post show, so I upvoted it. Cheers :)