Skip to content

Instantly share code, notes, and snippets.

@0xekez
Last active February 6, 2023 21:00
Show Gist options
  • Save 0xekez/98cf6abe3705f4ff83920b7d82c7f332 to your computer and use it in GitHub Desktop.
Save 0xekez/98cf6abe3705f4ff83920b7d82c7f332 to your computer and use it in GitHub Desktop.

Gas Free CosmWasm

Here we describe a simple system for interacting with CosmWasm contracts without paying gas. Instead of submitting transactions to a RPC node, addresses submit a signed ExecuteMsg to a third party and that party relays those messages to the appropriate smart contract. This third party may censor but not forge messages, and messages may be submitted as regular transactions to circumvent any censorship by the third party.

The System

Messages have the format:

{
	"payload": {
		"nonce": u64,
		"msg": ExecuteMsg,
		"expiration": Timestamp | null,
		"bech32_prefix": String,
		"to": Addr,
		"version": String
	},
	"signature": Binary,
	"pk": secp256,
}

To accept one of these messages from a smart contract:

  1. Validate that the payload is validly signed, or error.
  2. Validate that the payload has the correct nonce, or error.
  3. Validate that the payload has not expired, or error.
  4. Validate that the to field corresponds with the current contract.
  5. Validate that the version field corresponds to the verifier version.
  6. Set the message sender to the address corresponding to the provided public key.
  7. Call back into the contract's execute handler with this new sender and message.
let nonces: Map<String, u64> = Map::new("nonces");
let nonce = nonces.load(deps.storage, msg.pk)?;
deps.api.secp256k1_verify(msg.payload, msg.signature, msg.pk)?;
if msg.payload.nonce != nonce {
    return Err(NogasError::InvalidNonce)
}
if msg.payload.expiration.is_expired(&env.block) {
    return Err(NogasError::ExpiredPayload)
}
if msg.payload.to != env.contract.address {
	return Err(NoGasError::WrongReceiver)
}
if msg.payload.version != VERIFIER_VERSION {
	return Err(NoGasError::WrongVersion)
}
nonces.save(deps.storage, msg.pk, nonce + 1)?;

// call back into execute
info.sender = pk_to_addr(msg.pk)?;
execute(deps, info, env, msg.payload.msg)

This is permissionless in that any address may submit these signed messages.

Execution

image

The simplest design has a single web2 server with a private key submit messages on behalf of users. This server would likely be run by the developers. We are exploring designs for decentralizing execution:

We expect that executors will want to filter messages. For example, DAO DAO may start by only making votes free. This filtering should only happen on the executor, and not in contracts.

Conclusion

This system provides the same security and availability as an RPC node. The web2 server committing your signed messages to the chain may censor you, like an RPC node. No party can execute messages on your behalf so long so your private key is kept private.

@bekauz
Copy link

bekauz commented Feb 3, 2023

As far as the public key goes, are we going to be passing a compressed on uncompressed one?

@0xekez
Copy link
Author

0xekez commented Feb 6, 2023

As far as the public key goes, are we going to be passing a compressed on uncompressed one?

discussed in call: it seems like supporting both compressed and uncompressed is the same amount of work. we'll support both.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment