bitshares / bsips

BitShares Improvement Proposals and Protocols. These technical documents describe the process of updating and improving the BitShares blockchain and technical ecosystem.
https://bitshares.github.io
63 stars 86 forks source link

BSIP-SIGNED-MESSAGE #158

Open sschiessl-bcp opened 5 years ago

sschiessl-bcp commented 5 years ago
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 Message defined by the user
account_name String Account name linked to the memo public key
public_key String Any public key that is linked to the account and can be used to verify the signature
timestamp Double Unix timestamp
signature String Signature of the payload

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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

abitmore commented 5 years ago

Why timestamp is a double?

pmconrad commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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

pmconrad commented 5 years ago

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 commented 4 years ago

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

christophersanborn commented 3 years ago

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 commented 1 year ago

@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 commented 1 year ago

I think the meta data is to avoid replay attacks.

mellertson commented 1 year ago

I think the meta data is to avoid replay attacks.

Ahh that does make sense.