diff --git a/dips/dip-169.md b/dips/dip-169.md new file mode 100644 index 00000000..e594b756 --- /dev/null +++ b/dips/dip-169.md @@ -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 + +## 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( + tc: signer, + dd: signer, + vasp_addr: address, + amount: u64, + tier_index: u64 +) { + // Mint funds directly to VASP. + mint_to_vasp(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(alice, address_of(bob), amount_usd); + + // Then, Bob pays Alice in currency EUR. + // Previously, this line has to be a separate transaction. + pay(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( + payer: signer, payee: address, amount: u64, + metadata: vector, + metadata_signature: vector +){ + let msg = message(payer, amount, metadata); + verify_signature(compliance_key(payee), msg); + pay(payer, payee, amount, metadata); +} + +/// After: +fun dual_attested_payment( + payer: signer, payee: signer, amount: u64, + metadata: vector +){ + pay(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
, threshold: u64 } +public fun approve(approvers: vector, 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, + }, +} +``` + + + +### 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. + +```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` + +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, + secondary_signers: Vec, + }, +} +``` + + + +### 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 + +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. + +