Message hashes authenticate and notarize CoinSpark messages on the blockchain.

The hash of a CoinSpark message is calculated from the message’s content and embedded using message metadata inside the OP_RETURN of the bitcoin transaction which conveys that message. The hash serves two purposes:

  • It allows the recipient to be certain that the message was not modified in transit by the message delivery server. The sender calculates the hash from the message content and embeds it directly in the bitcoin transaction’s OP_RETURN metadata. To authenticate the content, the recipient can recalculate the hash from the content received from the delivery server and check that it matches the hash inside the transaction.
  • It provides an objective and permanent record of the message’s content on the bitcoin blockchain. For example, if the sender attaches a text message and PDF explaining that a transaction is for settlement of a particular invoice, the recipient cannot then claim that payment was not made. The sender can simply present the correspondence, point to the transaction on the blockchain, and show that the hash of the correspondence matches that inside the transaction. (This assumes that we can prove that the bitcoin address belongs to the recipient.)

The hash of a CoinSpark message is calculated from the following pieces of information:

  • Some random salt which is generated by the message sender’s wallet. This prevents dictionary attacks where simple but confidential messages could be identified by guessing their content and then checking if the corresponding hash can be found in the blockchain.
  • The MIME type of each piece of content, in UTF-8 encoding.
  • The (optional) file name of each piece of content, in UTF-8 encoding.
  • For each piece, the content itself.

More specifically, the hash is calculated from the 0x00-delimited concatenation of these items using the same SHA-256 hashing algorithm as the bitcoin protocol. The concatenation begins with the salt, then the following items for each piece of content: MIME type, file name, content. If there is no file name for an item, the empty string should be used instead.

Due to the 40 byte limit of OP_RETURNs in Bitcoin Core 0.9, there will usually not be space to include the full 32 byte SHA-256 hash digest within the message metadata. In that case, as much of the hash prefix as possible will be used.

To ensure consistency and ease of use, all of this logic is handled by the CoinSpark library – please see the example below.

Calculating a message hash

C/C++

unsigned char salt[32];
int saltChar;
CoinSparkMessagePart messageParts[2];
unsigned char messageHash[32];

for (saltChar=0; saltChar<sizeof(salt); saltChar++)
  salt[saltChar]=rand()%256;

messageParts[0].mimeType="text/plain"; // implies UTF-8 encoding
messageParts[0].mimeTypeLen=strlen(messageParts[0].mimeType);
messageParts[0].fileName=NULL;
messageParts[0].content="Payment for the attached invoice - Bob";
messageParts[0].contentLen=strlen(messageParts[0].content);

messageParts[1].mimeType="application/pdf";
messageParts[1].mimeTypeLen=strlen(messageParts[1].mimeType);
messageParts[1].fileName="Invoice AB123.pdf";
messageParts[1].fileNameLen=strlen(messageParts[1].fileName);
messageParts[1].content=RetrieveFileContent("files/Invoice AB123.pdf", &messageParts[1].contentLen);
  // assume we have some function like this that we can use

CoinSparkCalcMessageHash(salt, sizeof(salt), messageParts, 2, messageHash);

// The messageHash variable now contains the CoinSpark message hash

Java

byte[] salt = new byte[32];       
Random rnd=new Random();
rnd.nextBytes(salt);
    
CoinSparkMessage.ContentPart [] contentParts=new CoinSparkMessage.ContentPart[2];           
           
contentParts[0]=new CoinSparkMessage().new ContentPart();
contentParts[0].mimeType="text/plain"; // implies UTF-8 encoding
contentParts[0].fileName=null;
contentParts[0].content="Payment for the attached invoice - Bob".getBytes("UTF-8");
       
contentParts[1]=new CoinSparkMessage().new ContentPart();
contentParts[1].mimeType="application/pdf";
contentParts[1].fileName="Invoice AB123.pdf";
contentParts[1].content=file_get_contents("files/Invoice AB123.pdf");
    // assume we have some function like this that we can use
       
byte[] messageHash=CoinSparkMessage.calcMessageHash(salt, contentParts);

// The messageHash variable now contains the CoinSpark message hash

Javascript

salt=[]; // UInt8Array
for (var saltChar=0; saltChar<32; saltChar++)
  salt[saltChar]=Math.floor(Math.random()*256);
  
var messageParts=[
  {
    'mimeType': "text/plain", // implies UTF-8 encoding
    'fileName': null,
    'content': CoinSparkStringToUint8ArrayUTF8("Payment for the attached invoice - Bob")
      // all content must be provided as a UInt8Array
  },

  {
    'mimeType': "application/pdf",
    'fileName': "Invoice AB123.pdf",
    'content': RetrieveFileContent("files/Invoice AB123.pdf")
      // assume we have some function like this that we can use, which returns a UInt8Array
  }
];

messageHash=CoinSparkCalcMessageHash(salt, messageParts);

// The messageHash variable now contains the CoinSpark message hash as a UInt8Array

PHP

$salt='';
for ($saltChar=0; $saltChar<32; $saltChar++)
  $salt.=chr(rand()%256);
  
$messageParts=array(
  array(
    'mimeType' => "text/plain", // implies UTF-8 encoding
    'fileName' => null,
    'content' => "Payment for the attached invoice - Bob",
  ),

  array(
    'mimeType' => "application/pdf",
    'fileName' => "Invoice AB123.pdf",
    'content' => file_get_contents("files/Invoice AB123.pdf"),
  ),
);

$messageHash=CoinSparkCalcMessageHash($salt, $messageParts);

// The $messageHash variable now contains the CoinSpark message hash as a raw binary string

Python

salt=bytearray(random.getrandbits(8) for saltChar in range(32))
  
messageParts=[
  {
    'mimeType': "text/plain", # implies UTF-8 encoding
    'fileName': None,
    'content': "Payment for the attached invoice - Bob".encode('utf-8')
    # all content must be provided as a bytearray
  },

  {
    'mimeType': "application/pdf",
    'fileName': "Invoice AB123.pdf",
    'content': open("files/Invoice AB123.pdf", 'r').read()
  }
]

messageHash=CoinSparkCalcMessageHash(salt, messageParts)

# The messageHash variable now contains the CoinSpark message hash as a binary string

Ruby

salt=Random.new.bytes(32)
  
messageParts=[
  {
    'mimeType' => "text/plain", # implies UTF-8 encoding
    'fileName' => nil,
    'content' => "Payment for the attached invoice - Bob".encode('BINARY')
    # all content must be provided as a binary string
  },

  {
    'mimeType' => "application/pdf",
    'fileName' => "Invoice AB123.pdf",
    'content' => File.open('files/Invoice AB123.pdf', 'rb').read
  }
]

messageHash=CoinSparkCalcMessageHash(salt, messageParts)

# The messageHash variable now contains the CoinSpark message hash as a binary string