Skip to content
220 changes: 220 additions & 0 deletions dips/dip-169.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
---
dip: 169
title: Multi-Agent Transactions
authors: Emma Zhong, Tim Zakian, Sam Blackshear
status: Draft
type: Standard
created: 04/05/2021
issue: https://github.com/diem/dip/issues/169
---


# Summary

This DIP describes a new authentication scheme for Diem transactions: multi-agent transactions.

# Abstract

Currently in the Diem Framework, a transaction acts on behalf of a single on-chain account. However, there is no mechanism for multiple on-chain accounts to generate a single atomic transaction. This DIP presents a new scheme of transactions--multi-agent transactions--which act on behalf of multiple on-chain accounts. Multi-agent transactions leverage Move’s [_`signer`_](https://developers.diem.com/docs/move/move-signer/) type to allow an arbitrary number of atomic actions in one single transaction involving multiple on-chain accounts.

## Terminology:

* Primary signer: This is the account that the transaction is sent from. This account’s sequence number is used to determine whether the transaction is fresh, and is the account that pays gas. There is precisely one primary signer for every transaction.
* Secondary signer: This is any account that participates in a multi-agent transaction that isn’t the primary signer. There can be 0-N of these. The 0 case is a normal transaction today. There is no limit on the number of secondary signers but there is a limit on the number of signatures included in each transaction. See [Additional Checks During Transaction Validation](#Additional Checks During Transaction Validation) for more details.


# Motivation/Use Cases
Comment thread
emmazzz marked this conversation as resolved.

## Minting Directly to VASPs

Today in the Diem Framework, we need two transactions in order to mint money through designated dealers to VASPs. The first transaction is a transaction sent by the treasury compliance account to mint money into a designated dealer’s account. The second, decoupled transaction transfers the minted funds from the designated dealer’s account into a VASP’s account. This procedure requires two transactions due to the restriction that each transaction can only have one signer argument, despite this being a multi-party flow.

This restriction no longer exists in our proposed multi-agent scheme and we can perform both steps in a single atomic transaction (see code sample below). This atomicity implies that designated dealers no longer need custody services for the money they temporarily hold in their accounts, making it more affordable and much quicker to operate as a designated dealer. This should make it much easier for more DDs to onboard in the Diem ecosystem, providing more options for VASPs. It is highly likely that in the future, DDs primarily mint directly on requests from VASPs.

```rust
fun mint<Coin>(
tc: signer,
dd: signer,
vasp_addr: address,
amount: u64,
tier_index: u64
) {
// Mint funds directly to VASP.
mint_to_vasp<Coin>(tc, dd, vasp_addr, amount);
}
```



## Atomic Swaps

In order to do a currency exchange in the current scheme between two on-chain entities Alice and Bob, we need two transactions and possibly an escrow. The time gap between the two transactions can lead to potential problems such as resource lockup. With the new multi-agent transaction scheme, we only need one atomic transaction signed by both Alice and Bob, in which payments are sent in both directions (see the code sample below). In this case, both Alice and Bob have the same control over the transaction contents including exchange rates and expiration time.


```rust
// alice and bob agree on the values of amount_usd and amount_eur
// off-chain.
fun exchange(
alice: signer, bob: signer,
amount_usd: u64, amount_eur: u64
) {
// First, Alice pays Bob in currency USD.
pay<USD>(alice, address_of(bob), amount_usd);

// Then, Bob pays Alice in currency EUR.
// Previously, this line has to be a separate transaction.
pay<EUR>(bob, address_of(alice), amount_eur);
}
```



## Dually-attested Onchain Transactions


Currently, we execute a [_dual-attested payment_](https://dip.diem.com/dip-1/) transaction by including a signature of the payee as an argument to the transaction, and checking the validity of the signature on-chain if the payment is between two different VASPs and the amount is over a certain threshold. In order to accomplish that, we have to reconstruct and verify the signature ad-hoc on-chain. With the new scheme, we can simply require that the payee signs the entire transaction as well, and verify the signatures of both the payer and payee while validating the transaction. The code samples below show the before and after of what such a transaction might look like.

Some benefits brought by the multi-agent scheme in this scenario are:

* Extensibility: In the current scheme, our dual attestation protocol is very specific to travel rule payments. Extending this protocol to dual attestation on a different type of action would require another data format and signature scheme. The multi-agent transaction scheme generalizes this by allowing dual attestation on any action encodable by a Move script without having to define new data formats or signature schemes.
* Security: Since both the payer and the payee have to sign over the transaction, multi-agent scheme natively leads to more secure and robust dual-attestaion protocols, protecting against replayability issues present in today's protocol described in DIP-1.
* Earlier failure: Currently a bad signature in a travel rule transaction will be caught at execution time, and the transaction will abort. In the new scheme, the signature is checked at validation time and the transaction will be discarded instead.

```rust
/// Before:
fun dual_attested_payment<Coin>(
payer: signer, payee: address, amount: u64,
metadata: vector<u8>,
metadata_signature: vector<u8>
){
let msg = message(payer, amount, metadata);
verify_signature(compliance_key(payee), msg);
pay<Coin>(payer, payee, amount, metadata);
}

/// After:
fun dual_attested_payment<Coin>(
payer: signer, payee: signer, amount: u64,
metadata: vector<u8>
){
pay<Coin>(payer, address_of(payee), amount, metadata);
}
```

## K-of-N Approval Policy

We can use multi-agent transaction scheme to implement atomic K-of-N approvals. In the code sample below, we take in a vector of signers, check that every signer is an approver and that the number of signers is greater than the defined threshold. The advantage of this mechanism over multi-sig is that it supports multiple **on-chain** accounts and the approval process is recorded on-chain.

```rust
struct ApprovalPolicy has key { approvers: vector<address>, threshold: u64 }
public fun approve(approvers: vector<signer>, policy: &ApprovalPolicy)` {
// check that approvers is indeed a subset of policy.approvers.
assert(is_subset(&approvers, &policy.approvers), ENOT_APPROVERS);

// check that the number of approvers has passed the threshold.
assert(Vector::length(approvers) >= policy.threshold, ETHRESHOLD_NOT_REACHED);
}
```

## Arbitrary Atomic Actions in One Transaction

Besides the use cases mentioned above, our new multi-agent transaction scheme allows the primary signer to encode any combination of actions in a single atomic transaction. This added expressiveness opens up a lot more possibilities in the type of scenarios that we can implement. Some examples include [_delivery versus payment_](https://www.investopedia.com/terms/d/dvp.asp) (a crucial feature for securities settlement) and atomic administrative approval of sensitive actions (e.g., account creation).




# Code Changes to Support Multi-agent

## Diem Adapter Code Changes

### Add New Enum `RawTransactionWithData`

In order to preserve backward compatibility, we can’t add more fields representing secondary signers to `RawTransaction` struct. Thus we wrap `RawTransaction` in an enum `RawTransactionWithData`, which the primary and secondary signers will sign over to authenticate the transaction.


```rust
pub enum RawTransactionWithData {
MultiAgent {
raw_txn: RawTransaction,
secondary_signer_addresses: Vec<AccountAddress>,
},
}
```



### Add New Enum `AccountAuthenticator`

Previously each transaction can only have one signer, which is the sender of the transaction. In the multi-agent scheme, with the ability to have multiple signers, the transaction can potentially have multiple authenticators from different accounts and of different schemes. An `AccountAuthenticator` serves as the authenticator for one account. A `TransactionAuthenticator` can contain multiple `AccountAuthenticator`s, as shown in the next subsection.

Notice that `AccountAuthenticator` has a multi-signature variant `MultiEd25519`. `MultiEd25519` is a signature scheme an account uses to generate signatures. This is different from multi-agent, which is a new type of transaction.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that an account using MultiEd25519 can't be part of a MultiAgentTransaction?

I don't know much about which accounts use MultiEd25519, though. Is there a reference on that?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1. I think it would be useful to explain how to reconcile MultiAgent and MultiEd25519 if one is a sig scheme and another a txn scheme and why they are both variants of TransactionAuthenticator?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the difference is that MultiAgent does not require to combine signatures off-chain, and the combined signature is a single signature corresponding to a single account

@sblackshear sblackshear May 6, 2021

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, the addition of the multi-agent feature wouldn't give an attacker any more DDOS power, but I think we will need to tweak the rules/code a bit to ensure that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A check has been added to allow no more than 32 signatures. DIP has also been updated to talk about this check in section Additional Checks During Transaction Validation.


```rust
pub enum AccountAuthenticator {
/// Single signature
Ed25519 {
public_key: Ed25519PublicKey,
signature: Ed25519Signature,
},
/// K-of-N multisignature
MultiEd25519 {
public_key: MultiEd25519PublicKey,
signature: MultiEd25519Signature,
},
}
```



### Add a New Variant to `TransactionAuthenticator`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a New Variant or a new field?

@sblackshear sblackshear May 6, 2021

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Variant" is the right technical term for one of the potential values of a sum type like TransactionAuthenticator.


We have added one new variant to `TransactionAuthenticator` for the multi-agent transaction scheme. In the multi-agent scheme, the primary signer and all the secondary signers have to sign over `RawTransactionWithData::MultiAgent{ raw_txn, secondary_signer_addresses }`, to make sure that all parties agree to transact with each other.


```rust
pub enum TransactionAuthenticator {
/// Single signature
Ed25519 {
public_key: Ed25519PublicKey,
signature: Ed25519Signature,
},
/// K-of-N multisignature
MultiEd25519 {
public_key: MultiEd25519PublicKey,
signature: MultiEd25519Signature,
},
/// Multi-agent transaction.
MultiAgent {
primary_signer: AccountAuthenticator,
secondary_signer_addresses: Vec<AccountAddress>,
secondary_signers: Vec<AccountAuthenticator>,
},
}
```



### Additional Checks During Transaction Validation

In addition to signature checking, we also perform the following additional checks on signed multi-agent transactions during transaction validation:

* The number of secondary authenticators in the signed transaction is the same as the number of secondary signer addresses that all parties have signed over.
* There are no duplicates in the signers. In other words, primary signer and all the secondary signers have distinct account addresses. At Move level, this check guarantees that each `signer` value is backed by a distinct address.
* The total number of signatures included in the transaction authenticator does not exceed `MAX_NUM_OF_SIGS = 32` defined in authenticator code. For example, if a multi-agent transaction is signed two multi-sig accounts each with 20 signatures, then this transaction will be rejected since it includes more signatures (40 signatures) than allowed.



## Diem Framework Changes
Comment thread
emmazzz marked this conversation as resolved.

A new prologue function `multi_agent_script_prologue` is added to `DiemAccount.move` to validate multi-agent transactions. Below are the additional checks performed in `multi_agent_script_prologue`.


### Account Existence Checking in Prologue

Today we check in the prologue that the sender of the transaction has a `DiemAccount` resource under their address. With the ability to have multiple signers, we need to check that all of them have `DiemAccount` resources during the prologue.

### Authentication Key Checking in Prologue

Currently in the prologue, we check that the hash of the sender’s public key is equal to the authentication key that the sender has on chain. In the new scheme, we will need to perform the same check for public keys of the primary signer as well as all the secondary signers. In the `multi_agent_script_prologue`, we add a loop going through the two vectors containing addresses and public key hashes to perform the aforementioned check.