Руководство по Созданию подписанной транзакции
Tags: blockchain, aptos, tutorial, developer
Published: 2022-09-01 22:22:52

Все транзакции, выполняемые на блокчейне Aptos, должны быть подписаны. Это требование вводится сетью из соображений безопасности.

## Генерирование сообщения для подписи
Первым шагом в подписании транзакции является создание сообщения для подписания транзакции. Чтобы сгенерировать такое сообщение для подписи, вы можете использовать:

[REST API](https://fullnode.devnet.aptoslabs.com/v1/spec#/) ноды Aptos. Нода Aptos сгенерирует сообщение для подписи, подпись транзакции и отправит подписанную транзакцию в блокчейн Aptos. Однако такой подход небезопасен. См. раздел [Отправка транзакций в BCS против JSON](https://aptos.dev/sdks/transactions-with-ts-sdk).
Также смотрите руководство "[Ваша первая транзакция](https://dev.to/egormajj/rukovodstvo-dlia-razrabotchikov-aptos-vasha-piervaia-tranzaktsiia-8fk)", в котором объясняется этот подход.
Однако вы можете предпочесть, чтобы за создание подписанной транзакции отвечало ваше пользовательское приложение, например, аппаратный модуль безопасности . При таком подходе перед отправкой транзакций пользователь должен:
Сериализовать транзакции в байты, и
Подписать байты закрытым ключом учетной записи. Как работает учетная запись и закрытый ключ, смотрите в разделе Учетные записи.
В этом руководстве мы познакомимся с концепциями построения транзакции, генерации соответствующего сообщения для подписи с помощью BCS-сериализации, а также с различными методами подписи в Aptos.

> **СОВЕТ**
> Мы настоятельно рекомендуем вам использовать формат BCS для отправки транзакций в блокчейн Aptos.

## Обзор

Создание транзакции, готовой к исполнению на блокчейне Aptos, требует выполнения следующих четырех шагов:

1. Создайте необработанную транзакцию, `RawTransaction`, также называемую неподписанной транзакцией.
2. Сгенерируйте сообщение для подписи, содержащее соответствующее - (`prefix_bytes`), и создайте подпись необработанной транзакции с помощью закрытого ключа пользователя.
3. Создайте `Authenticator` и подписанную транзакцию, и
4. BCS-сериализация подписанной транзакции (не показана на диаграмме в разделе "Обзор").

> **ИНФОРМАЦИЯ**
> Примеры кода в этом разделе написаны на языке Typescript.

Беззнаковые транзакции известны как `RawTransactions`. Они содержат всю информацию о том, как выполнить операцию над учетной записью в Aptos. Но им не хватает соответствующей авторизации с помощью подписи или `Authenticator`.

В блокчейне Aptos все данные кодируются как [BCS](https://docs.rs/bcs/latest/bcs/) (Binary Canonical Serialization).

Aptos поддерживает множество различных подходов к подписанию транзакции, но по умолчанию используется подпись одного лица, использующего [Ed25519](https://ed25519.cr.yp.to/).

`Authenticator`, созданный во время подписания транзакции, дает разрешение блокчейну Aptos на выполнение транзакции от имени владельца учетной записи.

## Основные понятия

Необработанная транзакция состоит из следующих полей:

- **sender** (Адрес): Адрес учетной записи отправителя.
- **sequence_number** (uint64): Порядковый номер данной транзакции. Он должен совпадать с номером последовательности, хранящимся на учетной записи отправителя на момент выполнения транзакции.
- **payload**: Инструкции для блокчейна Aptos, включая публикацию модуля, выполнение функции скрипта или выполнение полезной нагрузки скрипта.
- **max_gas_amount** (uint64): Максимальная общая сумма газа, которую можно потратить на эту транзакцию. На учетной записи должно быть больше этой суммы газа, иначе транзакция будет отклонена при проверке.
- **gas_unit_price** (uint64): Цена, которая будет уплачена за единицу газа. Во время выполнения транзакции `total_gas_amount`, рассчитываемая следующим образом: `total_gas_amount = total_gas_units_consumed * gas_unit_price`, не должна превышать `max_gas_amount`, иначе транзакция будет прервана во время выполнения. `total_gas_units_consumed` представляет собой общее количество единиц газа, потребленных во время выполнения транзакции.
- **expiration_timestamp_secs** (uint64): Временная метка блокчейна, по истечении которой блокчейн отменит эту транзакцию.
- **chain_id** (uint8): Идентификатор сети блокчейн, на которой должна быть выполнена данная транзакция.

## BCS
Binary Canonical Serialization (BCS) - это формат сериализации, применяемый к необработанной ( неподписанной) транзакции. Описание целей разработки [BCS](https://docs.rs/bcs/latest/bcs/) см. в разделе BCS.

BCS не является самоописывающимся форматом. Поэтому, чтобы десериализовать сообщение, необходимо заранее знать его тип и структуру.

Пример того, как BCS сериализует строку, показан ниже:

```javascript
// 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]);
```
**Сообщение для подписи**
Байты BCS-сериализованной необработанной транзакции называются сообщением для подписи.

Кроме того, в Aptos любое содержимое, которое подписывается или хэшируется, солят уникальным префиксом, чтобы отличить его от других типов сообщений. Это делается для того, чтобы гарантировать, что содержимое может быть использовано только в предусмотренных скриптах. Сообщение для подписи RawTransaction имеет префикс `prefix_bytes`, который равен `sha3_256("APTOS::RawTransaction")`. Поэтому:
`signing_message = prefix_bytes|bcs_bytes_of_raw_transaction.`. `|` обозначает конкатенацию.

**Подпись**
Подпись - это результат хэширования подписываемого сообщения с помощью закрытого ключа пользователя. По умолчанию Aptos использует схему [Ed25519](https://ed25519.cr.yp.to/) для генерации подписи необработанной транзакции.

- Подписывая сообщение подписи закрытым ключом, клиенты доказывают Aptos Blockchain, что они разрешили выполнение транзакции.
- Aptos Blockchain проверит подпись с помощью открытого ключа учетной записи пользователя, чтобы убедиться, что представленная транзакция действительно подписана пользователем.

## Подписанная транзакция
Подписанная транзакция состоит из:

- необработанной транзакции и
- аутентификатор. Аутентификатор содержит открытый ключ пользователя и подпись необработанной транзакции.

Эта подписанная транзакция далее BCS-сериализуется , после чего подписанная транзакция готова к передаче в [интерфейс Aptos REST](https://fullnode.devnet.aptoslabs.com/v1/spec#/).

## Транзакции с несколькими подписями
Блокчейн Aptos поддерживает несколько методов подписания транзакций, включая однократную подпись, K-of-N multisig и другие.

Транзакция с несколькими подписями K-of-N означает, что для выполнения такой транзакции необходимо, чтобы по крайней мере K из N авторизованных подписантов подписали транзакцию и прошли проверку, проводимую в сети.

Подписи транзакций обернуты в `Authenticator`. Блокчейн Aptos проверяет транзакции, представленные пользователями, используя данные `Authenticator`. См. несколько примеров ниже:

На языке Typescript вот как выглядит аутентификатор с одной подписью:

```javascript
interface Authenticator {
  public_key: Uint8Array,
  signature: Uint8Array
}
```
Пример с несколькими подписями аутентификатора показан ниже:

```javascript
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
}
```

## Детальные шаги

1. Создание транзакции RawTransaction.
2. Подготовка сообщения для подписи и его подписание.
3. Создание аутентификатора и подписанной транзакции.
4. Сериализация SignedTransaction.


## Шаг 1. Создание транзакции RawTransaction
В приведенном ниже примере предполагается, что транзакция имеет полезную нагрузку в виде функции скрипта.

```javascript
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
  };
}
```

> **ПРИМЕЧАНИЕ**
> Сериализация `BCS`, показанная в приведенном выше коде, не совпадает с операцией сериализации BCS, показанной в разделе "Обзор".

## Шаг 2. Создание сообщения для подписи и его подписание

1. Сгенерируйте префикс (`prefix_bytes`) с [SHA3_256](https://en.wikipedia.org/wiki/SHA-3) хэшем байтов строки `APTOS::RawTransaction`.
2. Байты BCS сериализованной RawTransaction.
3. Конкатенация префикса и байтов BCS.
4. Подписание байтов закрытым ключом учетной записи.

```javascript
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);
```

## Шаг 3. Создание аутентификатора и подписанной транзакции

```javascript
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
};
```

## Шаг 4. Сериализация SignedTransaction

Сериализация SignedTransaction в байты с помощью BCS.

```javascript
const signedTransactionPayload = bcsSerializeSignedTransaction(signedTransaction);
```

## Отправка подписанной транзакции
Наконец, отправка транзакции с обязательным заголовком "Content-Type".

Чтобы отправить подписанную транзакцию в формате BCS, пользователь должен передать специальный заголовок, как показано в примере ниже:

```
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
```