Update cross-program and program address proposals (#10234)
This commit is contained in:
		| @@ -1,71 +0,0 @@ | ||||
| # Cross-Program Invocation | ||||
|  | ||||
| ## Problem | ||||
|  | ||||
| In today's implementation a client can create a transaction that modifies two accounts, each owned by a separate on-chain program: | ||||
|  | ||||
| ```text | ||||
| let message = Message::new(vec![ | ||||
|     token_instruction::pay(&alice_pubkey), | ||||
|     acme_instruction::launch_missiles(&bob_pubkey), | ||||
| ]); | ||||
| client.send_message(&[&alice_keypair, &bob_keypair], &message); | ||||
| ``` | ||||
|  | ||||
| The current implementation does not, however, allow the `acme` program to conveniently invoke `token` instructions on the client's behalf: | ||||
|  | ||||
| ```text | ||||
| let message = Message::new(vec![ | ||||
|     acme_instruction::pay_and_launch_missiles(&alice_pubkey, &bob_pubkey), | ||||
| ]); | ||||
| client.send_message(&[&alice_keypair, &bob_keypair], &message); | ||||
| ``` | ||||
|  | ||||
| Currently, there is no way to create instruction `pay_and_launch_missiles` that executes `token_instruction::pay` from the `acme` program. The workaround is to extend the `acme` program with the implementation of the `token` program, and create `token` accounts with `ACME_PROGRAM_ID`, which the `acme` program is permitted to modify. With that workaround, `acme` can modify token-like accounts created by the `acme` program, but not token accounts created by the `token` program. | ||||
|  | ||||
| ## Proposed Solution | ||||
|  | ||||
| The goal of this design is to modify Solana's runtime such that an on-chain program can invoke an instruction from another program. | ||||
|  | ||||
| Given two on-chain programs `token` and `acme`, each implementing instructions `pay()` and `launch_missiles()` respectively, we would ideally like to implement the `acme` module with a call to a function defined in the `token` module: | ||||
|  | ||||
| ```text | ||||
| use token; | ||||
|  | ||||
| fn launch_missiles(keyed_accounts: &[KeyedAccount]) -> Result<()> { | ||||
|     ... | ||||
| } | ||||
|  | ||||
| fn pay_and_launch_missiles(keyed_accounts: &[KeyedAccount]) -> Result<()> { | ||||
|     token::pay(&keyed_accounts[1..])?; | ||||
|  | ||||
|     launch_missiles(keyed_accounts)?; | ||||
| } | ||||
| ``` | ||||
|  | ||||
| The above code would require that the `token` crate be dynamically linked, so that a custom linker could intercept calls and validate accesses to `keyed_accounts`. That is, even though the client intends to modify both `token` and `acme` accounts, only `token` program is permitted to modify the `token` account, and only the `acme` program is permitted to modify the `acme` account. | ||||
|  | ||||
| Backing off from that ideal cross-program call, a slightly more verbose solution is to expose token's existing `process_instruction()` entrypoint to the acme program: | ||||
|  | ||||
| ```text | ||||
| use token_instruction; | ||||
|  | ||||
| fn launch_missiles(keyed_accounts: &[KeyedAccount]) -> Result<()> { | ||||
|     ... | ||||
| } | ||||
|  | ||||
| fn pay_and_launch_missiles(keyed_accounts: &[KeyedAccount]) -> Result<()> { | ||||
|     let alice_pubkey = keyed_accounts[1].key; | ||||
|     let instruction = token_instruction::pay(&alice_pubkey); | ||||
|     process_instruction(&instruction)?; | ||||
|  | ||||
|     launch_missiles(keyed_accounts)?; | ||||
| } | ||||
| ``` | ||||
|  | ||||
| where `process_instruction()` is built into Solana's runtime and responsible for routing the given instruction to the `token` program via the instruction's `program_id` field. Before invoking `pay()`, the runtime must also ensure that `acme` didn't modify any accounts owned by `token`. It does this by calling `runtime::verify_account_changes()` and then afterward updating all the `pre_*` variables to tentatively commit `acme`'s account modifications. After `pay()` completes, the runtime must again ensure that `token` didn't modify any accounts owned by `acme`. It should call `verify_account_changes()` again, but this time with the `token` program ID. Lastly, after `pay_and_launch_missiles()` completes, the runtime must call `verify_account_changes()` one more time, where it normally would, but using all updated `pre_*` variables. If executing `pay_and_launch_missiles()` up to `pay()` made no invalid account changes, `pay()` made no invalid changes, and executing from `pay()` until `pay_and_launch_missiles()` returns made no invalid changes, then the runtime can transitively assume `pay_and_launch_missiles()` as whole made no invalid account changes, and therefore commit all account modifications. | ||||
|  | ||||
| ### Setting `KeyedAccount.is_signer` | ||||
|  | ||||
| When `process_instruction()` is invoked, the runtime must create a new `KeyedAccounts` parameter using the signatures from the _original_ transaction data. Since the `token` program is immutable and existed on-chain prior to the `acme` program, the runtime can safely treat the transaction signature as a signature of a transaction with a `token` instruction. When the runtime sees the given instruction references `alice_pubkey`, it looks up the key in the transaction to see if that key corresponds to a transaction signature. In this case it does and so sets `KeyedAccount.is_signer`, thereby authorizing the `token` program to modify Alice's account. | ||||
|  | ||||
| @@ -1,169 +0,0 @@ | ||||
| # Program Keys and Signatures | ||||
|  | ||||
| ## Problem | ||||
|  | ||||
| Programs cannot generate their own signatures in `process_instruction` | ||||
| as defined in the [Cross-Program Invocations](cross-program-invocation.md) | ||||
| design. | ||||
|  | ||||
| Lack of programmatic signature generation limits the kinds of programs | ||||
| that can be implemented in Solana.  For example, a program cannot take | ||||
| ownership of a TokenAccount and later in a different transaction transfer | ||||
| the ownership based on the state of another program.  If two users want | ||||
| to make a wager in tokens on the outcome of a game in Solana, they must | ||||
| transfer tokens to some intermediary that will honor their agreement. | ||||
| Currently there is no way to implement this intermediary as a program | ||||
| in Solana. | ||||
|  | ||||
| This capability is necessary for many DeFi applications, since they | ||||
| require assets to be transferred to an escrow agent until some event | ||||
| occurs that determines the new owner. | ||||
|  | ||||
| * Decentralized Exchanges that transfer assets between matching bid and | ||||
| ask orders. | ||||
|  | ||||
| * Auctions that transfer assets to the winner. | ||||
|  | ||||
| * Games or prediction markets that collect and redistribute prizes to | ||||
| the winners. | ||||
|  | ||||
| ## Proposed Solution | ||||
|  | ||||
| The key to the design is two fold: | ||||
|  | ||||
| 1. Allow programs to control specific addresses, called Program | ||||
| Addresses, in such a way that it is impossible for any external | ||||
| user to generate valid transactions with signatures for those | ||||
| addresses. | ||||
|  | ||||
| 2. To allow programs to programatically control | ||||
| `KeyedAccount::is_signer` value for Program Addresses that are | ||||
| present in instructions that is invoked via `process_instruction()`. | ||||
|  | ||||
| Given the two conditions, users can securely transfer or assign | ||||
| ownershp of on chain assets to Program Addresses.  Once assigned, | ||||
| the program and only the program can execute instructions that | ||||
| refences a Program Address with `KeyedAccount::is_signer` set to | ||||
| true. | ||||
|  | ||||
| ### Private keys for Program Addresses | ||||
|  | ||||
| This address has no private key associated with it, and generating | ||||
| a signature for it is impossible.  While it has no private key of | ||||
| its own, the program can issue an instruction to set the | ||||
| `KeyedAccount::is_signer` flag for this address. | ||||
|  | ||||
| ### Hash based generated Program Addresses | ||||
|  | ||||
| All 256 bit values are valid ed25519 curve points, and valid ed25519 public | ||||
| keys.  All are equally secure and equally as hard to break. | ||||
| Based on this assumption, Program Addresses can be deterministically | ||||
| derived from a base seed using a 256 bit preimage resistant hash function. | ||||
|  | ||||
| Deterministic Program Addresses for programs follow a similar derivation | ||||
| path as Accounts created with `SystemInstruction::CreateAccountWithSeed` | ||||
| which is implemented with `system_instruction::create_address_with_seed`. | ||||
|  | ||||
| For reference the implementation is as follows: | ||||
|  | ||||
| ```rust,ignore | ||||
| pub fn create_address_with_seed( | ||||
|     base: &Pubkey, | ||||
|     seed: &str, | ||||
|     program_id: &Pubkey, | ||||
| ) -> Result<Pubkey, SystemError> { | ||||
|     if seed.len() > MAX_ADDRESS_SEED_LEN { | ||||
|         return Err(SystemError::MaxSeedLengthExceeded); | ||||
|     } | ||||
|  | ||||
|     Ok(Pubkey::new( | ||||
|         hashv(&[base.as_ref(), seed.as_ref(), program_id.as_ref()]).as_ref(), | ||||
|     )) | ||||
| } | ||||
| ``` | ||||
|  | ||||
| Programs can deterministically derive any number of addresses by | ||||
| using a keyword.  The keyword can symbolically identify how this | ||||
| address is used. | ||||
|  | ||||
| ```rust,ignore | ||||
| //! Generate a derived program address | ||||
| //!     * program_id, the program's id | ||||
| //!     * key_base, can be any public key chosen by the program | ||||
| //!     * keyword, symbolic keyword to identify the key | ||||
| //! | ||||
| //! The tuple (`key_base`, `keyword`) is used by programs to create user specific | ||||
| //! symbolic keys.  For example for the staking contact, the program may need: | ||||
| //!     * <user account>/<"withdrawer"> | ||||
| //!     * <user account>/<"staker"> | ||||
| //!     * <user account>/<"custodian"> | ||||
| //! As generated keys to control a single stake account for each user. | ||||
| pub fn derive_program_address( | ||||
|     program_id: &Pubkey, | ||||
|     key_base, &Pubkey, | ||||
|     keyword, &str, | ||||
| ) -> Result<Pubkey, SystemError> { | ||||
|  | ||||
|     // Generate a deterministic base for all program addresses that | ||||
|     // are owned by `program_id`. | ||||
|     // Hashing twice is recommended to prevent lenght extension attacks. | ||||
|     Ok(Pubkey::new( | ||||
|         hashv(&[hashv(&[program_id.as_ref(), key_base.as_ref(), keyword.as_ref(), | ||||
|             &"ProgramAddress11111111111111111111111111111"]).as_ref()]) | ||||
|     )) | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Using Program Addresses | ||||
|  | ||||
| Clients can use the `derive_program_address` function to generate | ||||
| a destination address. | ||||
|  | ||||
| ```rust,ignore | ||||
| //deterministically derive the escrow key | ||||
| let escrow_pubkey = derive_program_address(&escrow_program_id, &alice_pubkey, &"escrow"); | ||||
| let message = Message::new(vec![ | ||||
|     token_instruction::transfer(&alice_pubkey, &escrow_pubkey, 1), | ||||
| ]); | ||||
| //transfer 1 token to escrow | ||||
| client.send_message(&[&alice_keypair], &message); | ||||
| ``` | ||||
|  | ||||
| Programs can use the same function to generate the same address. | ||||
| Below the program issue a `token_instruction::transfer` from its | ||||
| own address as if it had a private key to sign the transaction. | ||||
|  | ||||
| ```rust,ignore | ||||
| fn transfer_one_token_from_escrow( | ||||
|     program_id: &Pubkey, | ||||
|     keyed_accounts: &[KeyedAccount] | ||||
| ) -> Result<()> { | ||||
|  | ||||
|  | ||||
|     //user supplies the destination | ||||
|     let alice_pubkey = keyed_accounts[1].key; | ||||
|  | ||||
|     // Deterministically derive the escrow pubkey. | ||||
|     let escrow_pubkey = derive_program_address(program_id, &alice_pubkey, &"escrow"); | ||||
|  | ||||
|     //create the transfer instruction | ||||
|     let instruction = token_instruction::transfer(&escrow_pubkey, &alice_pubkey, 1); | ||||
|  | ||||
|     // The runtime deterministically derives the key from the current | ||||
|     // program id and the supplied keywords. | ||||
|     // If the derived key matches a key in the instruction | ||||
|     // the `is_signed` flag is set. | ||||
|     process_signed_instruction(&instruction,  &[(&alice_pubkey, &"escrow")])? | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Setting `KeyedAccount::is_signer` | ||||
|  | ||||
| The addresses generated with `derive_program_address` are blinded | ||||
| and are indistinguishable from any other pubkey.  The only way for | ||||
| the runtime to verify that the address belongs to a program is for | ||||
| the program to supply the keyword used to generate the address. | ||||
|  | ||||
| The runtime will internally run  `derive_program_address(program_id, | ||||
| &alice_pubkey, &"escrow")`, and compare the result against the addresses | ||||
| supplied in the instruction. | ||||
		Reference in New Issue
	
	Block a user