How to make post on BitClout using BItClout API

How to make post on BitClout using BItClout API

Signing a transaction without BitClout Identity service

ยท

8 min read

Creating a BitClot post isn't that straightforward. If you have ever used Twitter API (aka Tweepy) you might recall that tweeting through the API was really easy and convenient. All you needed to do was to call the predefined functions of the twitter API. This isn't the case with BItClout. If you have to make post or any transaction (since post is also a transaction) you will have to go through 3 major steps:

Step 1: Get the post transaction Hex

Step 2: Sign the transaction Hex

Step 3: Submit the transaction.

While the step 1 and step 3 is fairly easy, the step 2 is where a lot of people find it difficult to get through. This is where this article can come handy for those people! You must note that this article is only for creating post from YOUR account only. You must not try to create any service (like schedule post or something) which will make post on other user's behalf. It is also recommended to use a test account to follow this tutorial because you will be using the seedHex of your account in your code. The seedHex in other language is as powerful as your BitClout seed phrase. If someone have access to your seedHex they can do ANYTHING with your account. If you are in crypto world from long time, you might know the risk of storing seed phrase (or seed Hex) on a computer or anywhere online. That's why you must use a test account to follow this tutorial (I repeat).

Before we get started, I want to let you know that this complete tutorial is in Python and it is must that you should be comfortable with the basics of python atleast.

  1. Getting the post transaction Hex:
    This step is fairly simple enough. All you need is to collect the transactionHex of the required post from the https://bitclout.com/api/v0/submit-post endpoint. Below is a python function that returns the transactionHex for a post
import json
import requests

def getPostTransaction(publicKey, body, imageUrl): 

  header = {
        "content-type": "application/json"
    }

  payload= {"UpdaterPublicKeyBase58Check": publicKey,
    "PostHashHexToModify": "",
    "ParentStakeID": "",
    "Title": "",
    "BodyObj": {"Body": body, "ImageURLs": imageUrl},
    "RecloutedPostHashHex": "",
    "PostExtraData": {},
    "Sub": "",
    "IsHidden":  False,
    "MinFeeRateNanosPerKB": 1000}

  res = requests.post(
        "https://bitclout.com/api/v0/submit-post", json=payload, headers=header)

  resJson = res.json()
  transactionHex = resJson["TransactionHex"]
  return transactionHex

In the above code snippet, publicKey refers to the public key of the account through which you want to make the post, body is a string referring to the post content and imageURL is a list type variable that stores the image URLs which you want to post on BitClout. You can pass imageURL to [] if you don't want top post image in your clout ( clout is to BitClout as tweet is to twitter )

  1. Signing the TransactionHex: The next and most important step is to sign the transaction. But wait, what does signing a transaction means ? Well, you are doing a transaction from an account so you can relate it as a bank account transaction where there must be a signature of the bank account owner. Does that makes sense ? The signature part is to make sure that the transaction is being done from the account owner himself and not from any thief*. BitClout has developed their own Identity service which is used to sign the transaction.

There are third party transaction signing APIs like this but as the author of the API himself says that the API could have vulnerabilities which can be a breach to your account security, you must NOT use the above mentioned API to sign the transaction. But don't worry, there are other ways to sign the transaction!

The TransactionHex can be signed without using Identity service as well. Below is a python script that can sign the TransactionHex in few milliseconds! Create a new python file sign.py which contains the following script:

import hashlib
import hmac

def get_hmac(key, data):
    return hmac.new(key, data, hashlib.sha256).digest()

def hmac_drbg(entropy, string):
    material = entropy + string
    K = b"\x00" * 32
    V = b"\x01" * 32

    K = get_hmac(K, V + b"\x00" + material)
    V = get_hmac(K, V)
    K = get_hmac(K, V + b"\x01" + material)
    V = get_hmac(K, V)

    temp = b""
    while len(temp) < 32:
        V = get_hmac(K, V)
        temp += V

    return temp[:32]

#######

g=(0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,
    0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8)
p=0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f
n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141

def point_add(point1, point2):
    # Returns the result of point1 + point2 according to the group law.
    if point1 is None:
        return point2
    if point2 is None:
        return point1

    x1, y1 = point1
    x2, y2 = point2

    if x1 == x2 and y1 != y2:
        return None

    if x1 == x2:
        m = (3 * x1 * x1) * pow(2 * y1, -1, p)
    else:
        m = (y1 - y2) * pow(x1 - x2, -1, p)

    x3 = m * m - x1 - x2
    y3 = y1 + m * (x3 - x1)
    result = (x3 % p, -y3 % p)

    return result

def scalar_mult(k, point):
    # Returns k * point computed using the double and point_add algorithm.
    result = None
    addend = point

    while k:
        if k & 1:
            # Add.
            result = point_add(result, addend)
        # Double.
        addend = point_add(addend, addend)
        k >>= 1

    return result

#######

def to_DER(r, s): # Signature to DER format
    r = bytes.fromhex(r)
    s = bytes.fromhex(s)
    if r[0] > 0x80:
        r = bytes.fromhex("00")+r
    if s[0] > 0x80:
        s = bytes.fromhex("00")+s
    res = bytes.fromhex("02"+hex(len(r))[2:]) + r + bytes.fromhex("02"+hex(len(s))[2:]) + s
    res = bytes.fromhex("30"+hex(len(res))[2:]) + res

    return res.hex()



def Sign_Transaction(seedHex, TransactionHex):
    s256 = hashlib.sha256(hashlib.sha256(bytes.fromhex(TransactionHex)).digest()).digest()
    drbg = hmac_drbg(entropy=bytes.fromhex(seedHex), string=s256)
    k = int.from_bytes(drbg, 'big')
    kp = scalar_mult(k, g)
    kpX = kp[0]
    r = kpX % n
    s = pow(k, -1, n) * (r * int(seedHex, 16)+int(s256.hex(), 16))
    s = s % n
    signature = to_DER(hex(r)[2:].zfill(64), hex(s)[2:].zfill(64))
    signed_transaction = TransactionHex[:-2] + hex(len(bytearray.fromhex(signature)))[2:] + signature

    return signed_transaction

The method Sign_Transaction(seedHex, TransactionHex) takes seedHex and TransactionHex as two argument. You can find the seedHex of your account in your browser storage. Just open https://bitclout.com/ > Dev tools > Application > Storage > Local Storage > https://identity.bitclout.com > users > Select the public key with which you want to post > seedHex

Note: Never share your seedHex with anyone. sharing seedHex is equivalent of sharing seed phrase

The second argument TransactionHex is the transaction which you want to sign.

On a side note, I didn't create the above code but I can make sure that the above code is secure! You can check the repo here. Note: You must use the code of this article ONLY and NOT from any other repo mentioned in this article. The code in this article is secure and none of your private info like seedHex is being shared with any third party. Being on the safe side, you must review the code of sign.py on your own.

  1. Submitting the signed post transaction: The third and last step is to submit the signed post TransactionHex. Below is a python code that submits the transaction using https://bitclout.com/api/v0/submit-transaction
import requests

def submitTransaction(signedTransactionHex):
    payload= {
        "TransactionHex": signedTransactionHex
    }
    response = requests.post(
        "https://bitclout.com/api/v0/submit-transaction", json=payload)

    return response.status_code

the submitTransaction method only takes signedTransactionHex as argument.

This is what the 3 required steps are to make post on BitClout. Summing up the above 3 steps, below is a 100% working code.


import os
import requests
import json
import hashlib
import hmac


def getPostTransaction(publicKey, body, imageUrl): 

  header = {
        "content-type": "application/json"
    }

  payload= {"UpdaterPublicKeyBase58Check": publicKey,
    "PostHashHexToModify": "",
    "ParentStakeID": "",
    "Title": "",
    "BodyObj": {"Body": body, "ImageURLs": imageUrl},
    "RecloutedPostHashHex": "",
    "PostExtraData": {},
    "Sub": "",
    "IsHidden":  False,
    "MinFeeRateNanosPerKB": 1000}

  res = requests.post(
        "https://bitclout.com/api/v0/submit-post", json=payload, headers=header)

  resJson = res.json()
  transactionHex = resJson["TransactionHex"]
  return transactionHex


def get_hmac(key, data):
    return hmac.new(key, data, hashlib.sha256).digest()

def hmac_drbg(entropy, string):
    material = entropy + string
    K = b"\x00" * 32
    V = b"\x01" * 32

    K = get_hmac(K, V + b"\x00" + material)
    V = get_hmac(K, V)
    K = get_hmac(K, V + b"\x01" + material)
    V = get_hmac(K, V)

    temp = b""
    while len(temp) < 32:
        V = get_hmac(K, V)
        temp += V

    return temp[:32]

#######

g=(0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,
    0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8)
p=0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f
n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141

def point_add(point1, point2):
    # Returns the result of point1 + point2 according to the group law.
    if point1 is None:
        return point2
    if point2 is None:
        return point1

    x1, y1 = point1
    x2, y2 = point2

    if x1 == x2 and y1 != y2:
        return None

    if x1 == x2:
        m = (3 * x1 * x1) * pow(2 * y1, -1, p)
    else:
        m = (y1 - y2) * pow(x1 - x2, -1, p)

    x3 = m * m - x1 - x2
    y3 = y1 + m * (x3 - x1)
    result = (x3 % p, -y3 % p)

    return result

def scalar_mult(k, point):
    # Returns k * point computed using the double and point_add algorithm.
    result = None
    addend = point

    while k:
        if k & 1:
            # Add.
            result = point_add(result, addend)
        # Double.
        addend = point_add(addend, addend)
        k >>= 1

    return result

#######

def to_DER(r, s): # Signature to DER format
    r = bytes.fromhex(r)
    s = bytes.fromhex(s)
    if r[0] > 0x80:
        r = bytes.fromhex("00")+r
    if s[0] > 0x80:
        s = bytes.fromhex("00")+s
    res = bytes.fromhex("02"+hex(len(r))[2:]) + r + bytes.fromhex("02"+hex(len(s))[2:]) + s
    res = bytes.fromhex("30"+hex(len(res))[2:]) + res

    return res.hex()



def Sign_Transaction(seedHex, TransactionHex):
    s256 = hashlib.sha256(hashlib.sha256(bytes.fromhex(TransactionHex)).digest()).digest()
    drbg = hmac_drbg(entropy=bytes.fromhex(seedHex), string=s256)
    k = int.from_bytes(drbg, 'big')
    kp = scalar_mult(k, g)
    kpX = kp[0]
    r = kpX % n
    s = pow(k, -1, n) * (r * int(seedHex, 16)+int(s256.hex(), 16))
    s = s % n
    signature = to_DER(hex(r)[2:].zfill(64), hex(s)[2:].zfill(64))
    signed_transaction = TransactionHex[:-2] + hex(len(bytearray.fromhex(signature)))[2:] + signature

    return signed_transaction


def submitTransaction(signedTransactionHex):
    payload= {
        "TransactionHex": signedTransactionHex
    }
    response = requests.post(
        "https://bitclout.com/api/v0/submit-transaction", json=payload)

    return response.status_code



if __name__ == "__main__":
  postBody = "In retrospect, it was inevitable ๐Ÿ’Ž๐Ÿคฒ " #don't dare change this body, Diamondhands won't like you
  imageURL = []
  seedHex = "YOUR_SEED_HEX"  # It is recommended to store your seedHex as environment variable
  publicKey = "YOUR_PUBLIC_KEY" 

  postTxn = getPostTransaction(publicKey, postBody, imageURL)
  signedTxn = Sign_Transaction(seedHex,postTxn)
  status = submitTransaction(signedTxn)

  if status == 200:
    print("Post done!")
  else:
    print("Error " + str(status))

Below is the result of the above code:

Screenshot from 2021-06-25 12-06-23.png

Congrats! You made a post on BitClout through your code.

If you like the article don't forget to let us know in the comments or maybe give a shout to DevsClout ? You can also join DevsClout discord server to chat with more devs who are building awesome projects on BitClout! We would love to hear back from you!

ย