Skip to content

How to build a blockchain transaction for Partisia Consent

This article teaches you how to build a transaction for a private blockchain. A transaction is an instruction from a user containing information used to change the state of the blockchain.

When the transaction has been signed, the signed transaction can be delivered to a blockchain node to put it on the blockchain.

All transactions are targeted towards a specific smart contract. The smart contract contains a state and actions that we can target with our transactions to change the state.

Transactions are defined by the transaction binary format.

When creating a serialized signed transaction we recommend you create it using one of the available client libraries. The specification can help you if you want to make your own implementation, for instance if you are targeting another programming language.

When the transaction is sent to the blockchain, the transaction bytes are delivered as a base64 string. In this document we interpret the bytes as hexadecimal for all examples.

Blockchain address and private key

To send a transaction, you will need a private key. On the blockchain we use the ECDSA signature scheme, using secp256k1 as the curve. An example of a private key on this curve is d6f4e13c010dbca23f647627d9b6085b816c933678f6026446f9a9596a2fb0d7. You should however create a new random key and keep it private.

Each private key has a blockchain address. This is similar to a public key and can thus be shared with others. The blockchain address is the identifier of the account, and will among many things be used to fetch the nonce. The blockchain address is created by hashing the public key and take the last 20 bytes (40 hex characters) and prepend it with 00. For the private key above, the corresponding blockchain address is

00cbb82f722528a63cc77abef0737ee693efecc995

In this example a transaction is built according to the transaction binary format. A private deployment does not require gas, so the gas cost will always be 0, so our transaction will be of the following form, where {x} denotes a variable.

{nonce}{validToTime}0000000000000000{contractId}{rpc}

Nonce

The nonce is specific to each account and can be retrieved from a blockchain node's REST API.

If the value of the nonce is 123456, the corresponding 8 byte big endian is 000000000001e240.

000000000001e240{validToTime}0000000000000000{contractId}{rpc}

Valid-to-time

The valid-to-time is a unix timestamp of when the transaction is valid to. If the transaction hasn't been included in a block before this time, it never will be, and the transaction will never take its effect on the blockchain. To create the valid-to-time you should get the current time and then add for how long you want the transaction to be valid (e.g. 60 seconds).

Example: If the current time is Tue Jan 16 2024 09:59:33 GMT+0100 (Central European Standard Time) we first translate this to milliseconds which is 1705395573437 and then finally add the 60 seconds (60.000 in milliseconds) which gives us the valid-to-time of 1705395633437. Representing this as 8 byte big endian gives us 0000018d11803d1d, which we put into the transaction:

{nonce}0000018d11803d1d0000000000000000{contractId}{rpc}

Contract ID / Address

The contract ID (sometimes also called contract address) is the ID of the smart contract that we are targeting with our transaction.

Example: For a transaction targeting the smart contract with id 044e9078383674cb8ea9994f13fa1fbb6b1766503b the transaction would look like:

{nonce}{validToTime}0000000000000000044e9078383674cb8ea9994f13fa1fbb6b1766503b{rpc}

Note: All ID's/addresses are already represented as hex strings throughout our system.

Creating the RPC

You can see how to create rpc for the consent management platform here.

Creating the signature

The signature for the transaction is an ECDSA signature, using secp256k1 as the curve, on a sha256 hash of the transaction and the chain ID of the blockchain.

First we look at the structure of the content that must be hashed:

<ToBeHashed> := {
    transaction: Transaction
    chainId: ChainId
}

<ChainId> := {
    len: 0xnn*4                              (big-endian)
    utf8: 0xnn*len                           
}

The content to be hashed consists of the transaction followed by the chain ID. You can see above how to serialize the transaction.

The final step is to create the actual signature of the hash, you can do this by using one of the below two methods.

  1. Apply the signature scheme yourself. This requires that you hold the private key. This is recommended for service providers.
  2. Send the hash to the key management service which will provide the signature. This method requires that the sender of the transaction has been registered through the SSO. This is only intended for end users.

The signature is serialized with the following format:

<Signature> := {
    recoveryId: 0xnn
    valueR: 0xnn*32                         (big-endian)
    valueS: 0xnn*32                         (big-endian)
}

and includes

  • A recovery id between 0 and 3 used to recover the public key when verifying the signature
  • The r value of the ECDSA signature
  • The s value of the ECDSA signature

Chain ID

The chain id is a unique identifier for the blockchain.

Example: The chain id for Partisia Demo Consent Platform is PARTISIA DEMO CONSENT CHAIN, which translates to

0000001e50415254495349412053414e44424f5820434f4e53454e5420434841494e

Full example

Now we look at a full example of how a transaction with signature would look like. In this example we want to give a consent, so we create a transaction from the following values

  • Nonce: 5
  • ValidToTime: 1705406564662
  • GasCost: 0
  • Address: 044e9078383674cb8ea9994f13fa1fbb6b1766503b
  • Rpc: Give consent (0000000100 when serialized)
  • ChainId: PARTISIA SANDBOX CONSENT CHAIN
  • The signature is created with private key d6f4e13c010dbca23f647627d9b6085b816c933678f6026446f9a9596a2fb0d7

which gives us the objects

<SignedTransaction> := {
    signature:   00a55a983ec8d40f63b5debdde42577b02186e4f9ee898031369d55f3e6c9be4bc93a280a488b49be7834491067a8e4f38795aeacfef87e8a2cbc7a86bacc2de4e
    transaction: Transaction
}

<Transaction> := {
    nonce:       0000000000000005                           
    validToTime: 0000018d12270936                    
    gasCost:     0000000000000000                        
    address:     044e9078383674cb8ea9994f13fa1fbb6b1766503b
    rpc :        0000000100
} 

<ToBeHashed> := {
    transaction: Transaction
    chainId: 0000001e50415254495349412053414e44424f5820434f4e53454e5420434841494e
}  

So the signed transaction is

00a55a983ec8d40f63b5debdde42577b02186e4f9ee898031369d55f3e6c9be4bc93a280a488b49be7834491067a8e4f38795aeacfef87e8a2cbc7a86bacc2de4e00000000000000050000018d122709360000000000000000044e9078383674cb8ea9994f13fa1fbb6b1766503b0000000100

which translated to base64 is

AKVamD7I1A9jtd693kJXewIYbk+e6JgDE2nVXz5sm+S8k6KApIi0m+eDRJEGeo5POHla6s/vh+iiy8eoa6zC3k4AAAAAAAAABQAAAY0SJwk2AAAAAAAAAAAETpB4ODZ0y46pmU8T+h+7axdmUDsAAAABAA==

Partisia All Rights Reserved © 2023