-
Notifications
You must be signed in to change notification settings - Fork 47
Add draft for DIP 169: Multi-agent Transactions #159
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
0f5d766
73e93f2
24afb69
d434db3
e598cc5
6909b84
3a55d97
f37a929
553da56
05344ea
65162a3
699fa27
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,261 @@ | ||||||
| --- | ||||||
| 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 scheme of user transaction: 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 agree on 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 essentially any arbitrary atomic actions in one transaction involving multiple on-chain accounts. | ||||||
|
emmazzz marked this conversation as resolved.
Outdated
|
||||||
|
|
||||||
| ## Terminology: | ||||||
|
|
||||||
| * Primary signer: This is the account that the transaction is sent from. This account’s sequence number is incremented, and is the account that pays gas. There is precisely one of these for every transaction. | ||||||
|
emmazzz marked this conversation as resolved.
Outdated
|
||||||
| * Secondary signer: This is any account that participates in a multi-agent transaction that isn’t the primary sender. There can be 0-N of these. The 0 case is a normal transaction today. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did we put a max on
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't put a max on |
||||||
|
|
||||||
| # Motivation/Use Cases | ||||||
|
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 tiered-mint transaction sent by the treasury compliance account to mint money to a designated dealer’s account. The second transaction transfers the minted funds from the designed dealer’s account into the VASP’s account. This procedure requires two transactions due to the restriction that each transaction can only have one signer argument. In the multi-agent scheme, this restriction no longer exists 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. | ||||||
|
emmazzz marked this conversation as resolved.
Outdated
|
||||||
|
|
||||||
| ```rust | ||||||
| fun mint<Coin>( | ||||||
| tc: signer, | ||||||
| dd: signer, | ||||||
| vasp_addr: address, | ||||||
| amount: u64, | ||||||
| tier_index: u64 | ||||||
| ) { | ||||||
| // First, TC mints to DD. | ||||||
| tiered_mint<Coin>( | ||||||
| tc, address_of(dd), amount, tier_index | ||||||
| ); | ||||||
|
emmazzz marked this conversation as resolved.
Outdated
|
||||||
|
|
||||||
| // Then, DD distributes funds to VASP. | ||||||
| pay<Coin>( | ||||||
| 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); | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
|
|
||||||
|
|
||||||
| ## Actual Dual-attested Transactions | ||||||
|
emmazzz marked this conversation as resolved.
Outdated
|
||||||
|
|
||||||
| 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 run some fairly complex code 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. | ||||||
|
emmazzz marked this conversation as resolved.
Outdated
|
||||||
|
|
||||||
| 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. And extending this protocol to dual attestation on a different type of action would require another ad-hoc 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. | ||||||
|
emmazzz marked this conversation as resolved.
Outdated
|
||||||
| * Security: Multi-agent scheme closes two security loopholes of our current protocol: | ||||||
| * Replayability: Since the payee only signs over the payer and the amount of the payment, today the payer can replay the transaction using old payee approvals. In the new scheme, this wouldn’t be possible since the payee has to sign over the sequence number of the transaction as well, disallowing any replay of the transaction. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would be a bit cautious with this statement. Isn't there protection against this? Isn't the receiver signing the full transaction which includes a unique sequence number, and if replayed by the sender, it will be rejected?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no protection against this. The recipient does not sign the full transaction, only an
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. This is not an issue with single-agent transactions, it is a problem with DIP-1 which could be fixed there, right? I would prefer to tone this down and mention the issue with DIP-1. Here, how about:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm fine with mentioning the motivation in more general terms as you suggest, although I do think a specific example helps. As an aside, it's not easy to fix the problem in DIP1 without this feature + CRSN's. If the approval message signs (e.g.) the payer's sequence number, the payer has difficulty sending other transactions concurrently with a TR transaction without invaliding the payee's approval. And it's not easy to sign over the currency without this feature because currency is a Move type, and Move doesn't allow you to reflect a type into a value that can then been signed. |
||||||
| * Currency: Today’s protocol does not let the payee commit to the currency type so the payer can reuse a payee approval for payment in one currency for payment in another with the same amount. Again, since the payee has to sign over the entire transaction including the currency type, this scenario would be impossible with the multi-agent scheme. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as above.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The approval message does not contain the currency, so this is indeed also a problem with the current scheme.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same |
||||||
| * 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); | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
|
|
||||||
|
|
||||||
| ## Arbitrary Atomic Actions in One Transaction | ||||||
|
|
||||||
| Besides the use cases mentioned above, our new multi-agent transaction scheme allows the sender to encode any arbitrary 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). | ||||||
|
emmazzz marked this conversation as resolved.
Outdated
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
| # Code Changes to Support Multi-agent | ||||||
|
|
||||||
| ## DiemVM Code Changes | ||||||
|
emmazzz marked this conversation as resolved.
Outdated
|
||||||
|
|
||||||
| ### Add New Enum `RawTransactionWithData` | ||||||
|
|
||||||
| In order to preserve backward compatibility, we can’t add more fields representing secondary signers to `RawTransaction` struct. Thus we add the following additional enum `RawTransactionWithData`, which the sender and secondary signers will sign over to authenticate the transaction. | ||||||
|
emmazzz marked this conversation as resolved.
Outdated
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
|
|
||||||
| ```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. And a `TransactionAuthenticator` can contain multiple `AccountAuthenticator`s, as shown in the next subsection. | ||||||
|
emmazzz marked this conversation as resolved.
Outdated
|
||||||
|
|
||||||
| 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. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1. I think it would be useful to explain how to reconcile
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||||||
|
|
||||||
| ```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` | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a New Variant or a new field?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||||||
|
|
||||||
| We have added one new variant to `TransactionAuthenticator` for the multi-agent transaction scheme. In the multi-agent scheme, the sender 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 { | ||||||
| sender: AccountAuthenticator, | ||||||
|
emmazzz marked this conversation as resolved.
Outdated
|
||||||
| 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, sender and all the secondary signers have distinct account addresses. | ||||||
|
emmazzz marked this conversation as resolved.
Outdated
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
| ## Diem Framework Changes | ||||||
|
emmazzz marked this conversation as resolved.
|
||||||
|
|
||||||
| A new prologue called `multi_agent_script_prologue` is added to `DiemAccount.move` to validate multi-agent transactions. | ||||||
| ```rust | ||||||
| fun multi_agent_script_prologue<Token: store>( | ||||||
| sender: signer, | ||||||
| txn_sequence_number: u64, | ||||||
| txn_sender_public_key: vector<u8>, | ||||||
| secondary_signer_addresses: vector<address>, | ||||||
| secondary_signer_public_key_hashes: vector<vector<u8>>, | ||||||
| txn_gas_price: u64, | ||||||
| txn_max_gas_units: u64, | ||||||
| txn_expiration_time: u64, | ||||||
| chain_id: u8, | ||||||
| ) | ||||||
| ``` | ||||||
|
|
||||||
| Following are the additional checks it performs. | ||||||
|
emmazzz marked this conversation as resolved.
Outdated
|
||||||
|
|
||||||
|
|
||||||
| ### 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. | ||||||
|
|
||||||
| ```rust | ||||||
| let i = 0; | ||||||
| while (i < num_secondary_signers) { | ||||||
| let secondary_address = *Vector::borrow(&secondary_signer_addresses, i); | ||||||
| assert( | ||||||
| exists_at(secondary_address), | ||||||
| Errors::invalid_argument(PROLOGUE_EACCOUNT_DNE) | ||||||
| ); | ||||||
| i = i + 1; | ||||||
| }; | ||||||
| ``` | ||||||
|
|
||||||
|
|
||||||
|
|
||||||
| ### 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 all the secondary signers as well. 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. | ||||||
|
|
||||||
|
|
||||||
| ```rust | ||||||
| let i = 0; | ||||||
| while (i < num_secondary_signers) { | ||||||
| let secondary_address = *Vector::borrow(&secondary_signer_addresses, i); | ||||||
| let signer_account = borrow_global<DiemAccount>(secondary_address); | ||||||
| let signer_public_key_hash = *Vector::borrow(&secondary_signer_public_key_hashes, i); | ||||||
| assert( | ||||||
| signer_public_key_hash == *&signer_account.authentication_key, | ||||||
| Errors::invalid_argument(PROLOGUE_EINVALID_ACCOUNT_AUTH_KEY), | ||||||
| ); | ||||||
| i = i + 1; | ||||||
| }; | ||||||
| ``` | ||||||
|
|
||||||
| # Feature Gating | ||||||
|
|
||||||
| Since there is no application of multi-agent transaction scheme in Diem yet, this feature is gated behind `DiemVersion = 10`. Multi-agent transactions are rejected when the Diem version is less than 10. | ||||||
|
emmazzz marked this conversation as resolved.
Outdated
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Uh oh!
There was an error while loading. Please reload this page.