Guidelines for Creating a Signed Transaction

All transactions performed on the Aptos blockchain must be signed. This is a requirement imposed by the network for security reasons.

Generating a Signing Message

The first step in signing a transaction is to generate a message to sign the transaction. To generate such a signing message, you can use:

Aptos node’s REST API. The Aptos node will generate a signature message, sign the transaction, and send the signed transaction to the Aptos blockchain. However, this approach is not secure. See Sending Transactions to BCS vs. JSON.
Also see the Your First Transaction guide for an explanation of this approach.
However, you may prefer to have your custom application, such as a hardware security module, be responsible for creating the signed transaction . With this approach, before sending transactions, the user must:
Serialize the transactions into bytes, and
Sign the bytes with the account private key. For how the account and private key work, see Accounts.
In this tutorial we will learn the concepts of building a transaction, generating the appropriate message to sign using BCS serialization, and the different signature methods in Aptos.

TIP
We highly recommend that you use the BCS format to send transactions to the Aptos blockchain.

Overview

Creating a transaction ready to execute on the Aptos blockchain requires the following four steps:

  1. Create a raw transaction, RawTransaction, also called an unsigned transaction.
  2. Generate a signature message containing the appropriate — (prefix_bytes), and create a signature for the raw transaction using the private user key.
  3. Create Authenticator and a signed transaction, and
  4. BCS serialization of the signed transaction (not shown in the diagram under «Overview»).

INFO
The code examples in this section are written in Typescript.

Unsigned transactions are known as RawTransactions. They contain all the information on how to perform a transaction on an account in Aptos. But they lack proper authorization with a signature or Authenticator.

In the Aptos blockchain, all data is encoded as BCS (Binary Canonical Serialization).

Aptos supports many different approaches to signing a transaction, but the default is a single person signature using Ed25519.

The Authenticator, created during transaction signing, gives permission to the Aptos blockchain to execute the transaction on behalf of the account owner.

Basic concepts

The raw transaction consists of the following fields:

  • sender (Address): The address of the sender account.
  • sequence_number (uint64): The sequence number of this transaction. It must match the sequence number stored on the sender’s account at the time of transaction execution.
  • payload: Instructions for the Aptos blockchain, including publishing a module, executing a script function or executing a script payload.
  • max_gas_amount (uint64): The maximum total amount of gas that can be spent on this transaction. The account must have more than this amount of gas or the transaction will be rejected at checkout.
  • gas_unit_price (uint64): The price to be paid for a unit of gas. During the execution of the transaction total_gas_amount, calculated as follows: total_gas_amount = total_gas_units_consumed * gas_unit_price, must not exceed max_gas_amount, otherwise the transaction will be aborted during execution. total_gas_units_consumed represents the total number of gas units consumed during the execution of the transaction.
  • expiration_timestamp_secs (uint64): The blockchain timestamp after which the blockchain will cancel this transaction.
  • chain_id (uint8): The identifier of the blockchain network on which this transaction is to be executed.

BCS

Binary Canonical Serialization (BCS) is a serialization format applied to a raw ( unsigned) transaction. See BCS section for description of BCS development goals.

BCS is not a self-describing format. Therefore, to deserialize a message it is necessary to know its type and structure beforehand.

An example of how BCS serializes a string is shown below:

// A string is serialized as: byte length + byte representation of the string. The byte length is required for deserialization. Without it, no way the deserializer knows how many bytes to deserialize.
const bytes: Unint8Array = bcs_serialize_string("aptos");
assert(bytes == [5, 0x61, 0x70, 0x74, 0x6F, 0x73]);
Enter fullscreen mode Exit fullscreen mode

Message for signature
The bytes of the BCS-serialized raw transaction are called the signature message.

In addition, in Aptos, any content that is signed or hashed is salted with a unique prefix to distinguish it from other message types. This is done to ensure that the content can only be used in the intended scripts. The RawTransaction signature message is prefixed with prefix_bytes, which is sha3_256("APTOS::RawTransaction"). Therefore:

Caption
A signature is the result of hashing the signed message with the user's private key. By default Aptos uses Ed25519 scheme to generate a signature of a raw transaction.

  • By signing the signature message with the private key, clients prove to Aptos Blockchain that they allowed the transaction to be executed.
  • Aptos Blockchain will verify the signature using the public key of the user account to ensure that the submitted transaction is indeed signed by the user.

Signed transaction.

A signed transaction consists of:

  • The raw transaction and
  • authenticator. The authenticator contains the user's public key and the signature of the raw transaction.

The signed transaction is further serialized by BCS and then the signed transaction is ready to be transferred into Aptos REST interface.

Transactions with multiple signatures

Aptos blockchain supports several methods of signing transactions including single signature, K-of-N multisig and others.

A K-of-N multisignature transaction means that at least K out of N authorized signers must sign the transaction and pass the network's validation to execute such a transaction.

Transaction signatures are wrapped in a Authenticator. The Aptos blockchain verifies transactions submitted by users using Authenticator data. See a few examples below:

In Typescript, this is what a single-signature authenticator looks like:

interface Authenticator {
  public_key: Uint8Array,
  signature: Uint8Array
}
Enter fullscreen mode Exit fullscreen mode

An example with multiple authenticator signatures is shown below:

interface  MultiEd25519PublicKey {
  // A list of public keys
  public_keys: Uint8Array[],
  // At least `threshold` signatures must be valid
  threshold: Uint8,
}

interface MultiEd25519Signature {
    // A list of signatures
    signatures: Uint8Array[],
    // 4 bytes, at most 32 signatures are supported.
    // If Nth bit value is `1`, the Nth signature should be provided in `signatures`. Bits are read from left to right
    bitmap: Uint8Array,
}

interface MultisigAuthenticator {
  public_key: MultiEd25519PublicKey,
  signature: MultiEd25519Signature
}
Enter fullscreen mode Exit fullscreen mode

Detailed steps

  1. Create a RawTransaction.
  2. Preparing a message to sign and signing it.
  3. Create the authenticator and the signed transaction.
  4. Serialization of the SignedTransaction.

Step 1. Creating a RawTransaction

The example below assumes that the transaction has a payload in the form of a script function.

interface AccountAddress {
  // 32-byte array
  address: Uint8Array
}

interface ModuleId {
  address: AccountAddress,
  name: string
}

interface ScriptFunction {
  module: ModuleId,
  function: string,
  ty_args: string[],
  args: Uint8Array[]
}

interface RawTransaction {
  sender: AccountAddress,
  sequence_number: number,
  payload: ScriptFunction,
  max_gas_amount: number,
  gas_unit_price: number,
  expiration_timestamp_secs: number,
  chain_id: number,
}

function createRawTransaction(): RawTransaction {
  const payload: ScriptFunction = {
    module: {
      address: hexToAccountAddress("0x01"),
      name: "AptosCoin"
    },
    function: "transfer",
    ty_args: [],
    args: [
      BCS.serialize(hexToAccountAddress("0x02")), // receipient of the transfer
      BCS.serialize_uint64(2), // amount to transfer
    ]
  }

  return {
    "sender": hexToAccountAddress("0x01"),
    "sequence_number": 1n,
    "max_gas_amount": 2000n,
    "gas_unit_price": 1n,
    // Unix timestamp, in seconds + 10 minutes
    "expiration_timestamp_secs": Math.floor(Date.now() / 1000) + 600,
    "payload": payload,
    "chain_id": 3
  };
}
Enter fullscreen mode Exit fullscreen mode

NOTE
The BCS serialization shown in the above code is not the same as the BCS serialization operation shown in the Overview section.

Step 2: Create a signature message and sign it

  1. Generate a prefix (prefix_bytes) with the SHA3_256 byte hash of the APTOS::RawTransaction string.
  2. BCS bytes of serialized RawTransaction.
  3. Concatenation of the prefix and BCS bytes.
  4. Signing bytes with private account key.
import * as Nacl from "tweetnacl";

function hashPrefix(): Buffer {
  let hash = SHA3.sha3_256.create();
  hash.update(`APTOS::RawTransaction`);
  return Buffer.from(hash.arrayBuffer());
}

function bcsSerializeRawTransaction(txn: RawTransaction): Buffer {
  ...
}

// This will serialize a raw transaction into bytes
function serializeRawTransaction(txn: RawTransaction): Buffer {
  // Generate a hash prefix
  const prefix = hashPrefix();

  // Serialize txn with BCS
  const bcsSerializedTxn = bcsSerializeRawTransaction(txn);

  return Buffer.concat([prefix, bcsSerializedTxn]);
}

const rawTxn = createRawTransaction();
const signature = Nacl.sign(hashRawTransaction(rawTxn), ACCOUNT_PRIVATE_KEY);
Enter fullscreen mode. Exit fullscreen mode.

Step 3: Create authenticator and signed transaction

interface Authenticator {
  public_key: Uint8Array,
  signature: Uint8Array
}

interface SignedTransaction {
  raw_txn: RawTransaction,
  authenticator: Authenticator
}

const authenticator = {
  public_key: PUBLIC_KEY,
  signature: signature
}

const signedTransaction: SignedTransaction = {
  raw_txn: rawTxn,
  authenticator: authenticator
};
Enter fullscreen mode Exit fullscreen mode

Step 4. SignedTransaction serialization

Serialize SignedTransaction into bytes using BCS.

const signedTransactionPayload = bcsSerializeSignedTransaction(signedTransaction);
Enter fullscreen mode Exit fullscreen mode

Sending a signed transaction

Finally, sending a transaction with the obligatory "Content-Type" header.

To send a signed transaction in BCS format the user has to transmit a special header as shown in the example below:

curl -X POST -H "Content-Type: application/x.aptos.signed_transaction+bcs" --data-binary "@path/to/file_contains_bcs_bytes_of_signed_txn" https://some_domain/transactions
Enter fullscreen mode Exit fullscreen mode

Оцените статью
devanswers.ru
Добавить комментарий