Proposal for "Note" inputs

Problem

Fuel’s unique transaction model provides substantially more flexibility for transactions, yet introduces some challenges. These challenges are particularly derived from Fuel’s multi-input model, as well as native support for alternative signature schemes using predicates.

One issue that has become apparent recently is the lack of a canonical “sender” of a transaction, breaking from Ethereum’s msg_sender model. More generally, Fuel applications are limited by the fact that applications can’t expect a unified signature scheme from all accounts (since predicate implementations may vary).

This document proposes a simple solution to address these issues.

Solution: “note” inputs

Transaction inputs represent the primary mechanism for authorizing the various roles and actions of a transaction. Therefore, it’s sensible to extend the functionality of the “input” set to support this new funcitonality.

A new type of input Note would be added to the existing input types (Coin, Contract and Message). Notes represent an arbitrary piece of data that is “signed” by a given account.

The InputNote would have the following structure:

name type description
owner byte[32] The address or predicate that is “authorizing” the predicate.
noteId byte[32] An identifier for the type of note.
witnessIndex uint16 Index of witness that authorizes spending the coin.
predicateGasUsed uint64 Gas used by predicate execution.
dataLength uint64 Length of note data, in bytes.
predicateLength uint64 Length of predicate, in instructions.
predicateDataLength uint64 Length of predicate input data, in bytes.
data byte[] The note data.
predicate byte[] Predicate bytecode.
predicateData byte[] Predicate input data (parameters).

Any account can include arbitrary notes in their transactions. Notes follow the same authentication rules as Coin and Message inputs (they must either be attached to witness signature of the full transaction ID, or approved by a predicate). However, given that Notes are not the outputs of previous transactions, they can be included arbitrarily.

Use Cases

Ownership of the script execution (msg_sender)

Problem

The Fuel standard library provides a function msg_sender, which aims to replicate the utility of msg.sender in Solidity. However, the Fuel transaction model makes this functionality less straightforward than in the EVM. While EVM transactions have a single “sender”, Fuel’s transaction model allows for multiple inputs to from various addresses to be included.

The current implementation of msg_sender simply checks the owner of all Coin and Message inputs. If all inputs have matching owners, then that address is returned, otherwise an error is returned.

This is problematic for applications that want to use multiple inputs, such as any application that sponsors gas, applications implementing multi-party exchanges (Griffy), etc.

Note-based solution

A standardized note ID would be used to represent the “owner” of the entire script execution. SDKs and wallets would automatically attach this note input, allowing smart contracts to easily verify the sender of external calls.

msg_sender implementation

The msg_sender function in the standard library would be updated to check for the standard

const NOTE_ID_SCRIPT_OWNER = 0xabcdef; // sha256("fuel-note:SCRIPT-OWNER")

pub fn caller_address() -> Result<Address, AuthError> {
    // Note: `input_count()` is guaranteed to be at least 1 for any valid tx.
    let inputs = input_count().as_u64();
    let mut candidate = None;
    let mut iter = 0;

    while iter < inputs {
        let type_of_input = input_type(iter);
        match type_of_input {
            Some(Input::Coin) => (),
            Some(Input::Message) => (),
            Some(Input::Note) => {
	            if (input_note_id(i) == NOTE_ID_SCRIPT_OWNER) {
		            return Ok(input_coin_owner(i).unwrap());
	            }
            },
            _ => {
                // type != InputCoin or InputMessage, continue looping.
                iter += 1;
                continue;
            }
        }

        // Rest of the function is unchanged
}

Per-call ownership (msg_sender)

It may be desirable for multiple contract calls to take place within a transaction from multiple “senders”. A standardized note could allow for one account to act as the “sender” of a specific call.

For example, it might be desired to update an oracle in the same transaction as a searcher liquidates a lending market. A oracle could sign over being the “owner” of a call to the oracle update, while the searcher would be the “primary” sender for the rest of the transaction.

Predicate-based “signatures”

Problem

Currently, it’s not possible to do “signature verification” for arbitrary predicate accounts, which may implement arbitrary signature verification schemes. This means that many applications will simply use the standard EC-recover functions for signature recovery. This means that other types of wallets (EVM wallets, multisigs, passkey wallets) can not work with this type of application, making these wallets “second-class citizens” in the Fuel ecosystem.

Prior art

This same issue is faced by smart-contract wallets in the Ethereum ecosystem, which can also not directly “sign messages”. This limitation has been addressed by ERC-1271, a standard which allows smart-contract wallets to define a function and implement their own logic for validating signed messages. This standard is supported by major wallets like Safe and Argent, as well as popular applications like CoW Swap, OpenSea and 1Inch.

A similar approach is not possible with predicates, as predicates do not provide “read” functions, only the ability to approve or reject an input.

Solution

Example

Imagine a DAO voting application, similar to Snapshot or Tally. While it’s possible for users to send each vote as a transaction, it’s better for users to make a simple gasless signature, and let one account submit all signed votes in a single transaction (and pay the gas fees).

The DAO application could provide a simple scheme for “vote” notes, and various wallets could generate some form of signature over these votes. For example, a multisig could provide sufficient signatures to approve the note, and an EVM wallet could sign over the note using an EVM-style signature.

One account would generate a transaction with all these notes as inputs, as well as the respective signatures and predicate data. This single transaction could be submitted with all votes counted together.

Note that the voting smart-contract would not need to implement any cryptographic verification logic, it can simply assess all notes included.

Multisigs for predicate-based wallets

Problem

The primary way to build multi-signature wallets on Fuel is to build them as a predicate. However, these predicates must currently implement the cryptography needed to validate each required signature. This means that multisigs can not support other wallet types (such as EVM wallets or passkeys) without directly implementing that cryptographic logic. Furthermore, there is no way for one multisig to be a signer on another multisig.

Solution

A multisig wallet like Bako could provide a standard note type for “approving” transactions. This would allow one predicate-based wallet to approve transactions, and act as an individual “signer” in another predicate based wallet.

Alternatives

One alternative that has been proposed for addressing the “owner” issue is the addition of a “policy” for ownership of the transaction (see the VM PR and spec PR). However, I believe this proposal is insufficient (doesn’t address the other use-cases described in this PR, and could potentially lead to security issues).

Potential exploit

Much discussion has been had about creating more flexible predicate-based wallets, such as the demos previously created by Harsh and myself. These wallets allow users to sign over some sort of “intent”, such as individual inputs, coin amounts, and/or the script bytecode. These intents can be then be assembled into a complete transaction by a “solver”.

The danger of the policy-based solution is that an attacker can essentially “take ownership” of any account that uses this type of predicate.

Example

John (using a predicate-based wallet) deposits 10 ETH into a vault smart contract, that uses this policy-based ownership policy.

Separately, John signs an intent to swap 1 ETH for 3000 USDC, and sends this intent to an intent mempool.

A malicious solver takes this intent, and constructs the following transaction:

  • 1 ETH is included as the input from John.
  • 3000 USDC is included as an output to John (solving the intent).
  • A policy is attached to the transaction, pointing to John’s 1 ETH input coin as the “owner” of the transaction.
  • The script of the transaction withdraws the 10 ETH from the vault (which is permitted because John is the “owner”) and sends it to the attacker’s address.

It is of course possible for this type of predicate to check & validate ownership policies, preventing this issue. However this shows that using policies for ownership makes wallets insecure by default, where as using inputs makes them secure by default.

3 Likes

@david really great post!

I agree with your remark that the policy pushes a lot of security issues on to the implementation of specific predicates, which creates two issues:

  • it will incur extra gas cost to the user { because of these checks, now predicates will consumer more gas }
  • the predicate developers need to spend a lot of time figuring out wether they have stopped all attack vectors

I have a question on how a transaction with multiple notes works.

the question being:

  • If a smart contracts sees that a transaction has 5 different notes, then how does it figure out which note should the use to determine msg_sender?
  • Is it by checking for the signing of some specific payload? If yes, then what if two notes are present with the signature on the same domain { contract panics in that case? }

If a smart contracts sees that a transaction has 5 different notes, then how does it figure out which note should the use to determine msg_sender?

In the msg_sender implementation I provided, it would simply pick the first input.

I’m not sure if there’s any vulnerabilities that would com from a user expecting to be the owner, but being superseded by a different note, are you aware of any?

If so, it could potentially be addressed at the wallet level (a predicate could enforce that only one ownership note is attached).

Hi David,

Thanks for sharing this proposal. It does great work addressing some of Fuel’s key challenges. The introduction of Note inputs is a clever solution to the lack of a canonical “sender” and brings much-needed flexibility for multi-input transactions. I also like how this aligns with ERC-1271, making integrating diverse wallets into Fuel’s ecosystem easier.

The real-world use cases, like DAO voting and multisig predicates, are spot-on and show the potential of this approach. Plus, the focus on security-first design is reassuring, especially when compared to the risks of policy-based ownership.

That said, there are a few areas I think could use more clarity:

  1. The complexity of Note inputs might be challenging for developers will the SDKs abstract some of this away? I should mention, for me there is not enough source for new developers about sway language.
  2. How do existing apps transition to the new msg_sender behavior? Will they write new contracts to implement it?
  3. Arbitrary note inclusion sounds powerful, but it raises concerns about transaction bloat are there plans to manage this efficiently?

Overall, I think this is a strong step forward, and I’m excited to see how it evolves. Looking forward to more updates!

Any account can include arbitrary notes in their transactions. Notes follow the same authentication rules as Coin and Message inputs (they must either be attached to witness signature of the full transaction ID, or approved by a predicate). However, given that Notes are not the outputs of previous transactions, they can be included arbitrarily.

I’m still wrapping my head around the problem that you’re trying to solve here, but this reminds me a lot of Minting Policies, which instead of a separate type of Output, is a separate type of Predicate which is used for minting new assets. You add the new assets in the outputs and it only approves if the conditions of the policy (predicate) are met.

It is also used for composability of predicates as well. For example, you could make the condition of minting be signatures of a specific set of addresses. Other predicates can then look that the specific asset is minted in the tx as proof that it’s signed by the quorum.

Is this the sort of problem you’re trying to solve? I’m not from the Ethereum space, so I’m not sure what the “msg_sender model” is.

I’m not sure that “minting policies” solves the problems described, but it does seem to solve a problem I’ve been thinking about a bit: how can we have predicates mint assets. Right now, a lot of our stateless, predicate-based applications still must use smart contracts simply to mint new assets. “Minting policies” sounds like it’s exactly that, but again it’s a different problem.

1 Like