> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ecrop.de/llms.txt
> Use this file to discover all available pages before exploring further.

# Proof of Action (PoA) Mechanism

> Sign sensitive API calls with the users private key

The Proof of Action (PoA) mechanism is a critical security feature in OmniOmni, ensuring the integrity and authenticity of sensitive actions performed by users and client systems. It provides a robust way to verify that a specific action was authorized by the rightful party. PoA is used in various scenarios, including:

* **Investment approvals**: Verifying that an investment request was authorized by the investor.
* **Transfer authorizations**: Confirming that a transfer of tokens was approved by both the sender and recipient.

<Note>In case you are not using one of ecrops solutions for party (end user) management, beware that when creating a party you have to submit its punlic key along with the parties identification details. This is the prerequisite for PoA to work. Chaging the public key also requires re-identifying the party.</Note>

## How PoA Works

PoA leverages cryptographic signatures and timestamps to verify the authenticity of actions. Here's a breakdown of the process:

1. Request Signing (Client-Side): When a user or client system initiates a sensitive action (e.g., approving an investment), the following data is concatenated into a single string, separated by periods (.):
   * **HTTP Method** (e.g., POST, PATCH)
   * **Request payload** (if JSON, remove any spaces)
   * **URI** (starting with RESTful resource names; any search parameters should be sorted in ascending order by parameter name)
   * **Timestamp** (UTC, ISO 8601 format)
   * **Device ID** (refers to a unique identifier for the client device making the request. If the request is made via a mobile application, the Device-ID is the mobile device’s unique identifier. If the request is made through an API, the Device-ID is represented by the Client-token used for authentication.)
2. Signature Generation: A SHA-256 hash of the concatenated string is generated and then encrypted using the private key associated with the acting party (either a user or a client system). The resulting signature is then Base64 encoded.
3. Request Headers: The following headers are included in the API request:
   * X-Signature: The Base64-encoded signature.
   * X-Signature-DateTime: The timestamp used in the signature generation.
   * X-Signature-DeviceId: The device ID (if used).
4. Signature Verification (Server-Side): Upon receiving the request, OmniOmni verifies the signature using the following steps:
   * Retrieve the public key associated with the acting party. (Clarify how the system retrieves the correct public key. Input needed from dev team.)
   * Recreate the signed data string using information from the request.
   * Verify the received signature using the public key of the end user.

## Key Considerations

* **Key Management**: *The security of the PoA mechanism relies heavily on the secure management of private keys. Ensure that private keys are never exposed and are stored securely.*
* **Timestamp Accuracy**: *Accurate timestamps are essential for preventing replay attacks. Ensure your system's clock is synchronized with a reliable time source.*
* **Error Handling**: *Implement robust error handling to manage scenarios where the signature is invalid or cannot be verified.*

# Technical Implementation

**Data format: JSON Web Signature (JWS) - RFC 7515**
JWS is a compact, URL-safe means of representing signed content using JSON data structures. It provides a way to digitally sign information, such as HTTP requests and responses, in a compact and secure manner.

**Signing method: JSON Web Algorithms (JWA) - RFC 7518**
JWA defines a set of algorithms for securing digital signatures and encryption of JSON data. It specifies the algorithms that can be used with JWS to provide integrity and authenticity.

## Client and Server Common Concepts and agreements

* **Key Pair algorithm**: We employ JWS with the RSA algorithm, specified as RS256 in JWA (JSON Web Algorithms - RFC 7518).
* **Key length**: 2048
* **Signing Algorithm**: RSA Signature-Scheme-with-Appendix (RSASSA)
* **Data to sign format**:  We join all following parts in a single string with “.” delimiter:

```Java Example to build the string that should be signed theme={null}
    public static String prepareSignaturePayload(String method, String payload, String url, String dateTimeString, String deviceId) {
        String data = new StringJoiner(".")
                .add(method)
                .add(payload)
                .add(url)
                .add(dateTimeString)
                .add(deviceId)
                .toString();
        return data;
        // result e.g.:  POST.{"state":"WAITING"}./test/echo-poa?name=John&state=SENDER_APPROVAL_WAITING.2024-01-22T23:54:07.145771486.Device-id
    }
```

<CodeGroup>
  ```Java Full Example with signing (Java) theme={null}
      public SignatureInfo sign(HttpMethod httpMethod, String url, Object payload, String deviceId, PrivateKey privateKey) throws JsonProcessingException, JOSEException {
          String payloadStr = payload == null ? "" : objectMapper.writeValueAsString(payload);
          String dateTimeString = dateFormat.format(new Date());
          String data = prepareSignaturePayload(httpMethod.toString(), payloadStr, url, dateTimeString, deviceId);
          JWSObject jwsObject = new JWSObject(
                  new JWSHeader.Builder(JWSAlgorithm.RS256).build(),
                  new Payload(data)
          );
          jwsObject.sign(new RSASSASigner(privateKey));
          String signature = jwsObject.serialize();
          signature = removePayloadFromCompactJws(signature);
          SignatureInfo signatureInfo = new SignatureInfo(dateTimeString, deviceId, signature);
          return signatureInfo;
      }
      public String prepareSignaturePayload(String method, String payload, String url, String dateTimeString, String deviceId) {
          return new StringJoiner(".")
                  .add(method)
                  .add(payload)
                  .add(url)
                  .add(dateTimeString)
                  .add(deviceId)
                  .toString();
      }
      public static String removePayloadFromCompactJws(String signature) {
          String[] parts = signature.split("\\.");
          return parts[0] + ".." + parts[2];
      }
  ```

  ```Dart Full Example with signing (Dart) theme={null}
  import 'dart:convert';
  import 'dart:io';
  import 'package:device_info_plus/device_info_plus.dart';
  import 'package:pointycastle/export.dart';
  import 'package:rsa_encrypt/rsa_encrypt.dart';
  import 'package:tacct/application/data/provider/api/api_manager_backend.dart';
  import 'package:tacct/application/data/provider/local_storage/local_storage_manager.dart';
  import 'package:intl/intl.dart' as intl;
  class SignUtils {
    static String signString(String data, RSAPrivateKey privateKey) {
      final signer = RSASigner(SHA256Digest(), "0609608648016503040201");
      signer.init(true, PrivateKeyParameter<RSAPrivateKey>(privateKey));
      return base64.encode(signer.generateSignature(utf8.encode(data)).bytes);
    }
    static Future<ApiManager> signRequest(
        {required String type,
        required String endpoint,
        required String body}) async {
      String cleanBody = correctRequestBody(body);
      String? token = await LocalStorageManager.getToken();
      String email = await LocalStorageManager.getEmail();
      String privateKey = await LocalStorageManager.getPrivateKey(email);
      var deviceInfo = DeviceInfoPlugin();
      String? deviceId;
      if (Platform.isIOS) {
        var iosDeviceInfo = await deviceInfo.iosInfo;
        deviceId = iosDeviceInfo.identifierForVendor;
      } else if (Platform.isAndroid) {
        var androidDeviceInfo = await deviceInfo.androidInfo;
        deviceId = androidDeviceInfo.id;
      }
      var now = DateTime.now().toUtc();
      var formatter = intl.DateFormat("yyyy-MM-dd HH:mm:ss");
      String currentTime = formatter.format(now);
      String payload = base64UrlEncode(
              utf8.encode("$type.$cleanBody.$endpoint.$currentTime.$deviceId"))
          .replaceAll('=', '');
      String header =
          base64UrlEncode(utf8.encode("{\"alg\":\"RS256\"}")).replaceAll('=', '');
      String finalString = "$header.$payload";
      var rsaKeyHelper = RsaKeyHelper();
      String signature = signString(
          finalString, rsaKeyHelper.parsePrivateKeyFromPem(privateKey));
      signature = signature.replaceAll('=', '');
      return ApiManager(
            token: token,
            signature: '$header..$signature',
            deviceId: deviceId,
            signatureTime: currentTime);
    }
  }
  ```
</CodeGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.