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

BSIP-SIGNED-MESSAGE #158

Open
sschiessl-bcp opened this issue Apr 3, 2019 · 17 comments
Open

BSIP-SIGNED-MESSAGE #158

sschiessl-bcp opened this issue Apr 3, 2019 · 17 comments

Comments

@sschiessl-bcp
Copy link
Collaborator

sschiessl-bcp commented Apr 3, 2019

BSIP: SIGNED-MESSAGE
Title: Signed Message
Authors:
  Stefan Schießl <stefan.schiessl@blockchainprojectsbv.com>
  Fabian Schuh <fabian.schuh@blockchainprojectsbv.com>
Status: Draft
Type: Informational (Client Protocol)
Created: 2019-04-03
Discussion: 

Abstract

This is an informal BSIP to define the format of a signed message to establish a common format.

Specification

Raw String Version 1

Plain String version with header and footer, similar to PGP message. Everything is a string, thus no quotation marks are present.

-----BEGIN BITSHARES SIGNED MESSAGE-----
<user_message>
-----BEGIN META-----
account=<account_name>
memokey=<memo_public_key>
block=<head_block_no>
timestamp=<datetime_string>
-----BEGIN SIGNATURE-----
<signature>
-----END BITSHARES SIGNED MESSAGE-----
Variable Description
user_message Message defined by the user
account_name Account name linked to the memo public key
public_key Any public key that is linked to the account and can be used to verify the signature
head_block_no Last irreversible block number at the time of signing
datetime_string Datetime formatted as a String (Date.toString())
signature Signature of the payload

The string payload for signing is

<user_message>
account=<account_name>
memokey=<public_key>
block=<head_block_no>
timestamp=<datetime_string>

represented as a string with visible characters

<user_message>\naccount=<account_name>\nmemokey=<memo_public_key>\nblock=\nhead_block_no>\ntimestamp=<datetime_string>

Example

-----BEGIN BITSHARES SIGNED MESSAGE-----
This is an example!
-----BEGIN META-----
account=sschiessl
memokey=BTS7zbtyYBnGAc4W4Kh9C1GKQZx51NGvJxEE17MXh3PadtD492VLw
block=36267739
timestamp=Wed, 03 Apr 2019 21:15:02 GMT
-----BEGIN SIGNATURE-----
2005fe3be3a436e8eb98fbfdef6277f38174ad8bd771fd9bb03468c340dbce7eff7b5579e34f3dc00074f33c46020b629e9d1f965e56fcb5972ef32910e4aed760
-----END BITSHARES SIGNED MESSAGE-----

JSON Version 1

Represented as a dictionary, a signed message in JSON Version 1 looks like

{
  payload: [
    "from",
    <account_name>,
    "key",
    <public_key>,
    "time",
    <timestamp>,
    "text",
    <user_message>
  ],
  signature: <signature>
}
Variable Description
user_message String
account_name String
public_key String
timestamp Double
signature String

The string payload for signing is

'["from",<account_name>,"key",<public_key>,"time",<timestamp>,"text",<user_message>]'

which corresponds to the stringified version of the JSON payload defined previously.

Example

{
  "payload": [
    "from",
    "sschiessl",
    "key",
    "BTS7zbtyYBnGAc4W4Kh9C1GKQZx51NGvJxEE17MXh3PadtD492VLw",
    "time",
    1554364619,
    "text",
    "This is an example!"
  ],
  "signature": "2005fe3be3a436e8eb98fbfdef6277f38174ad8bd771fd9bb03468c340dbce7eff7b5579e34f3dc00074f33c46020b629e9d1f965e56fcb5972ef32910e4aed760"
}
@pmconrad
Copy link
Contributor

pmconrad commented Apr 4, 2019

Please change BSIP type to "Informational (Client Protocol)".

Why is the payload field in JSON an array with alternating key/value strings instead of a proper object? (Ah, perhaps because fields in a JSON object do not have a well-defined order?)

IMO it's a bad idea to use a PGP-like plain string representation, precisely because PGP has had a lot of difficulties with this. At the very least, you must define precisely how whitespace and end-of-line characters are to be handled.

The signature field format isn't specified either, nor the signature algorithms.

@clockworkgr
Copy link
Member

Please change BSIP type to "Informational (Client Protocol)".

Why is the payload field in JSON an array with alternating key/value strings instead of a proper object? (Ah, perhaps because fields in a JSON object do not have a well-defined order?)

IMO it's a bad idea to use a PGP-like plain string representation, precisely because PGP has had a lot of difficulties with this. At the very least, you must define precisely how whitespace and end-of-line characters are to be handled.

The signature field format isn't specified either, nor the signature algorithms.

yep, JS arrays are deterministic order. objects (which would allow name/value pairs) are not

@xeroc
Copy link
Member

xeroc commented Apr 8, 2019

IMO it's a bad idea to use a PGP-like plain string representation, precisely because PGP has had a lot of difficulties with this. At the very least, you must define precisely how whitespace and end-of-line characters are to be handled.

Haha ... very good point, we ran into those problems too.
All fields should be stripped to not have whitespaces at either end of the string.

ECDSA is used on the secp256k1 curve. signature is presented in hexadecimal format and is prepended with signature recovery parameter. So, the signature is generated by hashing the stringified content, and signing the hash the very same way bitshares signs transactions.

@pmconrad
Copy link
Contributor

pmconrad commented Apr 8, 2019

What about embedded whitespace / EOL chars? What about encoding? JSON is utf-8 but plaintext is platform-dependent.
I strongly advise against this.

How is the string hashed? Transaction hashing prepends the chain ID, is that also required here?

@sschiessl-bcp
Copy link
Collaborator Author

I will check and fill in details for that (the plain text one is already existing in the UI that is why I want a post-published definition for it). Ideally the UI also switches away from it.

@xeroc
Copy link
Member

xeroc commented Apr 12, 2019

How is the string hashed? Transaction hashing prepends the chain ID, is that also required here?

Yes, it does - and IMHO should because accounts on different chains could be owned by different people.

The encoding, I suppose should be properly defined ... wouldn't it be easiest to allow UTF-8 everywhere and encode it as such?

@pmconrad
Copy link
Contributor

JSON strings can contain backslash escapes, which means there is no canonical representation either.

@abitmore
Copy link
Member

abitmore commented Aug 3, 2019

Why timestamp is a double?

@pmconrad
Copy link
Contributor

pmconrad commented Aug 7, 2019

IMO this BSIP should mention the existing message signing method used by python and JS libraries. Implementations should also accept messages signed using that old format, but produce signatures in the new format.

@sschiessl-bcp
Copy link
Collaborator Author

IMO this BSIP should mention the existing message signing method used by python and JS libraries. Implementations should also accept messages signed using that old format, but produce signatures in the new format.

Raw String Version 1 is the current one.

@abitmore
Copy link
Member

abitmore commented Aug 7, 2019

IMHO string is more read-able than a UNIX timestamp integer.

@pmconrad
Copy link
Contributor

pmconrad commented Aug 7, 2019

Raw String Version 1 is the current one.

Oh, right. Sorry.
In that case perhaps scrap the JSON version? Only adds confusion, doesn't really solve canonicalization issues etc.

@abitmore
Copy link
Member

This should be a draft in the repository but not only an issue.

@christophersanborn
Copy link
Member

IMHO the metadata should be dropped from the spec, as it effectively makes the metadata part of the signature. In other words, to verify the signature of a message, the verifier would need to be in possession of both the signature and the metadata. This means the following script between Valarie Validator and Bob Signer wouldn't work:

Valarie:  If you are indeed Bob, please provide me a signature of the message,

          "I am Bob, presenting myself on this day to Valarie."

Bob:      OK, [signs message], here it is: "20ff2fea55cb4a...."

The problem is, Valerie cannot validate this signature, unless Bob also provides the metadata, which at best is cumbersome, and at worst may not be possible, if they are using a communication protocol that does not allow for this extra data.

A validator should be able to validate a known message with the signature bytes alone.

@mellertson
Copy link

mellertson commented Dec 18, 2022

@christophersanborn I know this issue has been open a long time, but I worked up an example for my own learning and thought I would share. Of course, if you implemented a BitShares app using this modified protocol you couldn't send messages from your app to existing BitShares users who are using the web UI, for example. So, I wouldn't recommend it. But, I think it's good just to see how to verify a signature independant from the messaging protocol.

It seems like the meta data is redundant to me, because all that data can be derived from the blockchain itself. But, I am curious to learn from others what the thought process is behind including the meta data. Maybe I'm missing something.

import json, binascii, unittest
from graphenebase import BrainKey, memo
from graphenebase.ecdsa import sign_message, verify_message

class message_signing_Tests(unittest.TestCase):
	receiver_brainkey = BrainKey(
		brainkey='DANLI ARTHEL SOREDIA BASKER IDYL SAMMY RUFTER IDEATE TITULUS CUTUP BORGH PERIOST ARETE BANDIE MIDE BIPEDAL',
		prefix='TEST',
		sequence=0,
	)
	sender_brainkey = BrainKey(
		brainkey='BELTER QUEASY TAGRAG PAXILLA COUXIA PAPISM HELM WISTE STILTY INDITER STARNEL CURIATE MAFFIA LOTS RHABDUS BEDWAY',
		prefix='TEST',
		sequence=0,
	)
	salt = datetime.utcnow()

	def setUp(self) -> None:
		super().setUp()
		self.text = 'What is the Ultimate Question of Life, the Universe, and Everything?'
		self.payload = [
			'from',
			'user-one',
			'key',
			str(self.sender_brainkey.get_public_key()),
			'time',
			str(self.salt),
			'text',
			self.text,

		]
		self.payload_encoded = json.dumps(self.payload, separators=(",", ":"))

	def test_sign_verify_and_encrypt_payload(self):
		# sign the JSON encoded payload
		self.signature = binascii.hexlify(
			sign_message(
				self.payload_encoded,
				str(self.sender_brainkey.get_private_key()),
			)
		).decode('ascii')
		# verify the signature
		self.sender_pubkey = binascii.hexlify(
			verify_message(
				self.payload_encoded,
				binascii.unhexlify(self.signature),
			)
		).decode('ascii')
		self.assertEqual(
			repr(self.sender_brainkey.get_public_key()),
			self.sender_pubkey,
		)
		# receiver encrypts the message into a BitShares memo.
		self.memo_encrypted = memo.encode_memo(
			self.sender_brainkey.get_private_key(),
			self.receiver_brainkey.get_public_key(),
			self.salt,
			self.payload_encoded,
		)
		# print message
		msg = '-----BEGIN BITSHARES SIGNED MESSAGE-----\n'
		msg += f'{self.text}\n'
		msg += '-----BEGIN SIGNATURE-----\n'
		msg += f'{self.signature}\n'
		msg += '-----END BITSHARES SIGNED MESSAGE-----'
		print(msg)

Which, would yield this output:

-----BEGIN BITSHARES SIGNED MESSAGE-----
What is the Ultimate Question of Life, the Universe, and Everything?
-----BEGIN SIGNATURE-----
1f4ed8d6e7205245469ba54cdb7899523ccc6ee7274b256d3d337b6501d0c8d670072c2a30ae20e16f234c919b3872c35388590daaf25246240f246e9671714d88
-----END BITSHARES SIGNED MESSAGE-----

@abitmore
Copy link
Member

I think the meta data is to avoid replay attacks.

@mellertson
Copy link

I think the meta data is to avoid replay attacks.

Ahh that does make sense.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants