Skip to content

Manual Key Management

Introduction

STRATO allows users to send transactions that are prepared and signed manually, rather than by STRATO itself. This functionality is available through a separate endpoint from the standard transactions endpoint. Transactions must be prepared according to the Ethereum transaction specification, including the correct user nonce, and r, s, & v ECDSA signature values. See Preparing a Transaction for details on these steps.

The endpoint bypasses STRATO's key vault to sign transactions, so it is not required that an address-key pair already exist within the vault to make a valid transaction. Therefore, users must store and manage their own public-private keys, Ethereum addresses, and account nonces. User account information is only stored within STRATO when a user has successfully posted to the STRATO /key endpoint. If a user does not wish their information to be stored within STRATO, than a user should not use the /key endpoint and sign all transactions manually as described within this article. Since a user's key is only retrieved for completing transactions, a user may still interact with all other features of STRATO without using the /key endpoint.

Transaction Endpoint Behavior

Standard STRATO Transaction Flow

Manual Transaction Flow

While key management may be done outside of STRATO, request authorization is still managed by the OAuth client set up at boot time. However, using manually signed transactions allows users to separate their OAuth identity with their public and private-keys associated with that identity. Learn more about managing your STRATO identity in the User Identity & Authorization page.

Warning

Since STRATO only manages user keys that are in its key vault, if a user loses or forgets their private-key that has not been stored by STRATO, it is irrecoverable by any mechanism within STRATO or by any representative of BlockApps. Furthermore any data associated that key such as nonce or address is also lost. A user's public-key is only recoverable when all r, s & v values are included in a transaction payload. Therefore, use this feature at your own risk, bearing in mind to keep your credentials in a safe place where they will be remembered.

Prerequisites

Manually signing transactions requires the understanding of several cryptographic and Ethereum related topics:

  • Ethereum transaction types.
  • Public-Private key encryption.
    • Elliptic Curve Encryption (ECDSA).
  • Keccak256 Hashing.
  • ABI Contract Type Encoding.
  • RLP Encoding/Serialization.

API Endpoint

Manually signed transactions are sent to the raw transaction endpoint.

Endpoint

POST https://<strato_address>/bloc/v2.2/transaction/raw

The endpoint takes the arguments of a standard signed Ethereum transaction, as well as the user's address and additional metadata elements. The transaction is sent in the request's body as a JSON object.

Request Body

  • nonce: number *
    • The current nonce for this user address (key). This will be the nonce used for this transaction.
    • See Account Nonce.
  • gasPrice : number *
    • The number of tokens the user will spend per unit of gas for this transaction.
    • Ignored for networks with gas disabled.
  • gasLimit : number *
    • The maximum amount of gas the user would want to use for this transaction.
    • Ignored for networks with gas disabled.
  • to : string *
    • Optional.
    • The Ethereum account address prefixed with 0x.
    • The address to which this transaction will send tokens to/call a function on.
    • null for contract creation.
  • value : number *
    • The number of tokens to send to the to account of the transaction.
    • Ignored for networks with gas disabled.
    • Typically 0 for contract creation and function call transactions.
  • chainId : string
    • Optional.
    • The chainID of the private chain on which to execute this transaction.
    • See Note on Private Chains.
  • initOrData : string *
    • The transaction data payload for this transaction, encoded as per the Ethereum protocol for transactions using the EVM.
    • See Preparing a Transaction.
  • address : string
    • The 20-byte Ethereum address of the user account signing this transaction.
  • r : number *
    • The first 32 bytes of the ECDSA signature generated from the signed raw transaction.
    • Formatted in hexadecimal.
  • s : number *
    • The last 32 bytes of the ECDSA signature generated from the signed raw transaction.
    • Formatted in hexadecimal.
  • v : 27 | 28 *
    • Optional if address is included.
    • The recovery ID (parity bit) of the ECDSA signature, for public-key recovery.
    • v = 27 + recovery ID.
    • When omitted, the public-key associated with private-key used to sign the transaction will be irrecoverable.
  • metadata : object
    • Optional.
    • Key-value metadata elements to include in the transaction.
    • See Metadata Elements to learn more about how to use the metadata property.

*This property is part of the Ethereum transaction specification.

Example Payload for a Contract Creation Transaction

{ 
  "nonce": 23,
  "gasPrice": 1,
  "gasLimit": 1230000,
  "value": 0,
  "to": null,
  "initOrData": "<SolidVM-contract-code>",
  "r": "6d4151d112cce107c33e50145478f714411b286d28b82d01f0a162b2916ade6a",
  "s": "210fbbb6f778aefb332aae55cc9179759751fc69ca9f32139da0599da8b6fbb4",
  "v": 28,
  "address": "86d28b82d5478f714411b201f0a162b2916ade6a",
  "metadata":  {
    "VM": "SolidVM",
    "name": "SimpleStorage",
    "args": "(5)"
  }
}

Preparing a Transaction

Users must first prepare an unsigned Ethereum transaction via the Ethereum Transaction Protocol.

Deriving a User Address

User addresses on STRATO follow the Ethereum Account Protocol. An address is associated with a single elliptic curve public-key generated by a user. To generate the proper ethereum address, follow the instructions below:

  1. Generate a public-private key pair using the secp256k1 curve. Ensure that the key is not compressed.
  2. Convert the public-key to hexadecimal format (if not already). This should be exactly 64 bytes long.
  3. Generate the Keccak256 hash of the public-key.
  4. Extract the last 40 bytes (20 hex-characters) of the key's hash as the user's address.

Ethereum Address Derivation Steps

Use this guide for a simple walkthrough of generating an ethereum address in a terminal environment.

Account Nonce

Every account has an associated nonce which is a counter for the number of transactions this user account has already performed. An account's nonce starts at 0 when it has executed its first transaction. For each transaction sent, the nonce is incremented by 1.

Since an account's nonce is stored with the user's identity in STRATO's vault, a transaction sent to the transaction/raw endpoint will not update or store an account's nonce, since it has no internal record of the account. The user sending manually signed transactions must increment and store the correct nonce for a given account.

Encoding Transaction Data

The instructions declaring what a transaction does is contained within the initOrData field (named data in Ethereum). Shown here are the steps to properly form each transaction type.

Token Transfer

The transaction must contain a to address, and a value in tokens to transfer to the specified address.

No data is needed in the initOrData field of the transaction.

Contract Creation

The transaction to address must be null or omitted since the transaction will be creating a new contract account.

The code of the contract to be created is provided in the initOrData field:

  • For EVM contracts, this is the compiled EVM bytecode for the corresponding solidity smart contract.
    • The STRATO API provides the bloc/v2.2/contracts/compile endpoint to allow users to easily compile their smart contracts for the EVM. See the Complete API Documentation for more information.
  • For SolidVM contracts, this is the UTF-8 encoded smart contract Solidity code.

Important

When creating the transaction payload to sign with a private-key, the contract code must be in the proper encoding as listed above. However, when sending the final request to the STRATO API, the encoded contract data must be converted to hexadecimal as the value in the request body's initOrData field.

Contract Creation Transaction Payload

Function Calls

The transaction to address is the address of the contract account being called.

SolidVM

Function name and arguments for contracts using SolidVM are provided in the metadata property of the API request body. Therefore the initOrData property is left empty for SolidVM function calls.

EVM

Function calls on contracts that use the EVM must follow the Ethereum function call standard, as described below. The signature of the function being called and the arguments being passed to the function must individually be encoded and then concatenated with each other. This final payload is the data in the initOrData field.

  1. Encode the function signature. Typically called the "function selector".
    • A functions signature is its name and its argument types. E.g. function(type_0,type_1,...type_n) returns (type_2).
    • Generate the Keccak256 hash of the function signature.
    • Extract the first 4 bytes (8 hexadecimal characters) as the Function Selector.
  2. Encode the function arguments.
  3. Concatenate the function selector with each encoded argument value in order.
    • functionSelector + ABI(arg_0) + ABI(arg_1) ... + ABI(arg_n)

Important

When creating the transaction payload to sign with a private-key, the function call information must be in the proper encoding as listed above. However, when sending the final request to the STRATO API, the encoded function call information must be converted to hexadecimal as the value in the request body's initOrData field.

EVM Function Call Transaction Payload

See this explanation from Medium.com for a more in-depth explanation of creating function-call transactions.

Creating the Raw Transaction Payload

Once the proper initOrData payload has been created, the raw transaction payload can be prepared. The raw transaction payload is an array of the following properties in this order:

  • nonce
  • gasPrice
  • gasLimit
  • to
  • value
  • initOrData
  • chainId

The final transaction payload is encoded with the RLP serialization method. RLP is a method of serialization that allows arbitrary data structures to be encoded in a standardized, compact way.

RLP serialization can be reviewed in detail at the Ethereum Wiki.

Example Raw Transaction Payloads

{
  "nonce": 0,
  "gasPrice": 1,
  "gasLimit": 1230000,
  "value": 100,
  "to": "0x211446238914bb9dfa81913f961c50ff79556c1d",
  "initOrData" : "",
}

RLP_List = [0, 1, 1230000, 100, "0x211446238914bb9dfa81913f961c50ff79556c1d", ""]
{
  "nonce": 0,
  "gasPrice": 1,
  "gasLimit": 1230000,
  "value": 0,
  "to": null,
  "initOrData" : "<UTF-8-SolidVM-contract>",
}

RLP_List = [0, 1, 1230000, 0, null, "<UTF-8-SolidVM-contract>"]
{
  "nonce": 0,
  "gasPrice": 1,
  "gasLimit": 1230000,
  "value": 0,
  "to": "0x612dcdb6ae3c9e1c69ad83f1f0b262e91d33852d",
  "initOrData" : "",
}

RLP_List = [0, 1, 1230000, 0, "0x612dcdb6ae3c9e1c69ad83f1f0b262e91d33852d", ""]

Signing a Transaction

Once the raw transaction payload has been created, a user must sign it. This cryptographically links the transaction payload with the sender. It ensures the transaction's origin and contents are unchanged from creation to execution. Furthermore, any user may inspect a transaction to validate its origin and contents, provided they have the public-key for the key-pair.

Sign Transaction:

  1. Generate the Keccak256 hash of the RLP-serialized transaction payload.
  2. Sign the transaction hash with the user's private-key.
    • Signatures can be either standard ECDSA (64 bytes) or recoverable ECDSA (65 bytes).

Extract signature values:

Once a transaction is signed, it will produce a 64 or 65 byte output signature - the first and second 32 bytes are the values r and s respectively. These values are used in the API payload in their respective fields.

The ECDSA Recovery ID v is the last byte of the output if the signature is recoverable (65-byte signature). This value will be a 0 or 1. Per the Ethereum standard, the v value should be either 27 or 28 (corresponding to 0 and 1 respectively), so when providing the recovery ID to STRATO, add 27 to the v value. v is optional in the API request since it may be inferred from the address field. v allows the public-key of the key-pair used for this transaction to be recovered instantly, rather than using the public-key recovery algorithm.

Signing a Transaction

Metadata Elements

The metadata property in a request payload is used to send STRATO extra information about the request outside of the Ethereum transaction standard. Typically, metadata elements are used in conjunction with SolidVM contracts. You can learn about the metadata property in the API Basics Article.

Using SolidVM

Any contract creation or function call that uses SolidVM must have the VM property to "SolidVM".

{
  "metadata": {
    "VM": "SolidVM"
  }
}
Contract Creation

For contract creations, the name of the contract to be created in the provided code collection must also be included:

{
  "metadata": {
    "VM": "SolidVM",
    "name": "MyContract"
  }
}

Function Arguments

Function arguments (for contract constructors or function calls) are provided in the args property as a comma separated list (no spaces between list elements) surrounded with parentheses.

{
  "metadata": {
    "VM": "SolidVM",
    "args": "(1)"
  }

Function Calls

For function calls, the function name must be provided.

{
  "metadata": {
    "VM": "SolidVM",
    "funcName": "MyFunc",
    "args": "(42,100)"
  }

Sending a Transaction

The transaction/raw endpoint is identical to the standard /transaction endpoint in its query parameters and response format. Since the transaction/raw endpoint only accepts a single transaction, it will only respond with a single response object, rather than an array of results. Visit the transaction endpoint documentation for full details.

Send a transaction by putting the values derived from preparing the transaction in the previous steps into their respective fields of the request body.

Important

Remember to encode the initOrData value into hexadecimal for a contract creation call or EVM function call.

Example

curl -X POST \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{ 
    "nonce": 23,
    "gasPrice": 1,
    "gasLimit": 1230000000,
    "value": 100,
    "to": "0x211446238914bb9dfa81913f961c50ff79556c1d",
    "initOrData": "",
    "r": "6d4151d112cce107c33e50145478f714411b286d28b82d01f0a162b2916ade6a",
    "s": "210fbbb6f778aefb332aae55cc9179759751fc69ca9f32139da0599da8b6fbb4",
    "address": "4673cb1451756a70de6f2040de3cb1451756af20"
  }' \
"https://<strato_address>/strato/v2.3/transaction/raw?resolve=true"
curl -X POST \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{ 
    "nonce": 24,
    "gasPrice": 1,
    "gasLimit": 1230000000,
    "value": 0,
    "to": null,
    "initOrData": "<hex-encoded-contract>",
    "r": "6d4151d112cce107c33e50145478f714411b286d28b82d01f0a162b2916ade6a",
    "s": "210fbbb6f778aefb332aae55cc9179759751fc69ca9f32139da0599da8b6fbb4",
    "address": "4673cb1451756a70de6f2040de3cb1451756af20",
    "metadata":  {
      "VM": "SolidVM",
      "name": "SimpleStorage",
      "args": "(5)"
    }
  }' \
"https://<strato_address>/strato/v2.3/transaction/raw?resolve=true"
curl -X POST \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{ 
    "nonce": 25,
    "gasPrice": 1,
    "gasLimit": 1230000000,
    "value": 0,
    "to": "0x446238914bc50ff79556c1d21b9dfa81913f9611",
    "initOrData": "",
    "r": "6d4151d112cce107c33e50145478f714411b286d28b82d01f0a162b2916ade6a",
    "s": "210fbbb6f778aefb332aae55cc9179759751fc69ca9f32139da0599da8b6fbb4",
    "address": "4673cb1451756a70de6f2040de3cb1451756af20",
    "metadata":  {
      "VM": "SolidVM",
      "funcName": "set",
      "args": "(5)"
    }
  }' \
"https://<strato_address>/strato/v2.3/transaction/raw?resolve=true"

Response

The transaction hash and transaction result. Note the response field is the Ethereum standard encoded response from the transaction.

A response from a contract creation transaction:

{ 
    "status": "Success",
    "hash": "936f9ea1f29edd4f71af8004c115c90687b9dedc4b99c3e653c6a06f7341e6c1",
    "txResult": {
      "deletedStorage": "",
      "status": "success",
      "contractsDeleted": "",
      "gasUsed": "0000000000000000000000000000000000000000000000000000000000000123",
      "stateDiff": "",
      "time": 0.0006,
      "kind": "SolidVM",
      "chainId": "0x4574c8c75d6e88acd28f7e467dac97b5C60c3838d9dad993900bdf402152228e",
      "response": "",
      "blockHash": "356fa535acf1396be5e8d8fec3745b4331c1531b59efbdb61e86aee8cbdc00d8",
      "transactionHash": "fbf20ab650b690b5626b8d3b8e9719460de61d7d87e24a58174338cee5c5ef08",
      "etherUsed": "0000000000000000000000000000000000000000000000000000000000000123",
      "newStorage": "",
      "message": "Success!",
      "trace": "",
      "contractsCreated": ""
    },
    "data": null
  }

Note

Because transaction responses are returned in the Ethereum format, STRATO does fill in the response data field, which normally contains information such as the contract compilation info, address, and more. Most of these values are still available in txResult however they must be extracted or decoded from their respective fields.

Note on Private Chains

The creation of STRATO Private Chains requires a user's key to be present in the key vault. Therefore any user that manages their own keys cannot create a private chain. However any user may send transactions to a private chain, regardless of how their keys are managed.

Resources

  1. Creating Ethereum Transactions
  2. Ethereum ECDSA Signatures
  3. Keccak Hashing Algorithm