Skip to content

ERC-1271 : Standard Signature Validation Method for Contracts  #1271

@PhABC

Description

@PhABC
Contributor

Simple Description

Many blockchain based applications allow users to sign off-chain messages instead of directly requesting users to do an on-chain transaction. This is the case for decentralized exchanges with off-chain orderbooks like 0x and etherdelta. These applications usually assume that the message will be signed by the same address that owns the assets. However, one can hold assets directly in their regular account (controlled by a private key) or in a smart contract that acts as a wallet (e.g. a multisig contract). The current design of many smart contracts prevent contract based accounts from interacting with them, since contracts do not possess private keys and therefore can not directly sign messages. The proposal here outlines a standard way for contracts to verify if a provided signature is valid when the account is a contract.

See EIP draft.

Activity

changed the title [-]Standard Signature Validation Method for Contracts [/-] [+]ERC-1271 : Standard Signature Validation Method for Contracts [/+] on Jul 28, 2018
shrugs

shrugs commented on Jul 28, 2018

@shrugs

Realizing that this is more than just validating signatures; we're actually asking the question "given this action, does the caller have the ability to it, given this proof"?

so perhaps the better metaphor is

isValidAction(bytes _action, bytes _proof) {}

where the default behavior might be to treat _action as a Bouncer data hash and _proof as a signature. But like we noticed before, they could be anything.

Although this implies that the proof handling and the access control are part of the same function/contract where they could potentially be different, although I'm not sure how that separation would generalize at all (i.e. in the case of ECDSA signatures, the validity is just the recovery, which produces an address and the access control is a "does this address have this permission" check against the _action data).

Thoughts, @PhABC?

PhABC

PhABC commented on Jul 28, 2018

@PhABC
ContributorAuthor

That's a good point! A signature is usually used to guarantee that a given address allows something, but other schemes not relying on signature methods could also achieve the same goal.

Pinging @alexvandesande & @abandeali1 for comments.

abandeali1

abandeali1 commented on Aug 2, 2018

@abandeali1

I personally prefer using a 32 byte hash over an _action byte array. Any action should be able to be represented as a hash, and this really simplifies the implementation details. You're probably going to end up hashing _action and verifying a signature at some point anyways. Any extra logic could live in the caller contract.

I could see it making sense to use the word proof instead of signature, though.

PhABC

PhABC commented on Aug 2, 2018

@PhABC
ContributorAuthor

@abandeali1

The reason why we opted for a dynamic byte array is because the called contract might request specific data that the caller contract is not aware of.

For instance, imagine you use a smart account that has some key management properties, where different keys have different roles (RBAC style). Now imagine that the private key on your phone can sell some game assets, but can't any other types of tokens. Your ledger private key can sell any type of asset. If the caller contract (e.g. 0x contract) only passes the hash of the message, the smart account has no way of knowing what the asset being traded is. The smart account would see two hashes that were indeed signed by two private key you own, but it would have no way of verifying if the action signed is valid based on it's own internal conditions. In this case, your smart account would either need to allow all your private keys to exchange assets via 0x (let them sign hashes) or prevent them all.

In general, what i'm trying to say is that I think there could always be additional rules not encompassed by the caller contract that the called contract requires.

shrugs

shrugs commented on Aug 2, 2018

@shrugs

Likewise, my worry is that a hash loses information that may be necessary for validation.

That said, I'm also worried about the complexity of passing arbitrary data via a bytes call; we've just accidentally re-created the need for abi encoding/decoding and whoops, we should probably just use solidity for that, so now we're back where we started, creating context-specific methods.
It may make more sense to simply restrict this to hash + proof validation, and nothing more? that covers bouncer and identity contracts, the two main cases I'd like to use this technique for.

abandeali1

abandeali1 commented on Aug 2, 2018

@abandeali1

You can always encode any extra data about the hash in the signature field. For example:

function isValidSignature(bytes32 hash, bytes signature) external {
    uint8 signatureType = uint8(signature[0]);
    bytes32 msgHash = keccak256(hash, signatureType);
    if (signatureType == 0) {
        // Do something specific to signatureType
        return _isValidSignature(msgHash, signature);
    } else if ...
}

You can imagine adding any amount of extra data to signature in a similar way. This keeps the simplest implementation simple, but it's still pretty flexible if necessary.

shrugs

shrugs commented on Aug 2, 2018

@shrugs

@abandeali1 I'm not convinced that overloading the signature ("proof") argument is a ton better than bytes action, bytes proof.

shrugs

shrugs commented on Aug 4, 2018

@shrugs

@PhABC any feedback on restricting this to hash+signature based validation?

abandeali1

abandeali1 commented on Aug 4, 2018

@abandeali1

@shrugs my point is that it can be done with just a hash+signature, but I wouldn't expect that to be frequently used.

frangio

frangio commented on Aug 5, 2018

@frangio
Contributor

Thanks for writing this up @PhABC, it's quite good.

The discussion around @abandeali1's suggestion is very interesting, but we need to show some examples of usage in order to best decide on fixed or arbitrary size _data (i.e. the message that is signed).

I think a good example to analyze is that of an m-of-n multisig smart account. A valid signature for this account could be the concatenation of m (signature, signer) pairs, where the m signers are in the set of n signers, and each signature is valid for the message and respective signer. I think this is enough motivation for the signature to be of arbitrary size.

Now suppose that a family of messages can only be considered signed when the full set of n signers approve. It's common to increase the required number of signatures for large value transfers, so I think it would make sense for the signature scheme to have something analogous to that. The validity of the signature then depends on some parameter in the message being signed. Per @abandeali1's suggestion, the parameter should be included as part of the signature, but then the contract would have to additionally verify that the parameter in the signature is indeed the one contained in the message. If the message is hashed, then the entire message has to also be included in the signature in order to verify the hash. That would be a lot of redundant bytes being passed around. IMO this justifies having the message be of arbitrary size, and not necessarily a hash.

Do you all agree the scenario described makes sense?


A mostly unrelated question about the EIP. Would it be valid for an implementation of isValidSignature to change its behavior depending on the caller? I have the feeling that this should be forbidden by the spec ("MUST NOT"), but unfortunately I don't think there's a way to enforce it in the API.

frangio

frangio commented on Aug 5, 2018

@frangio
Contributor

I forgot to comment on the suggestion to use the names "action" and "proof". Although I kind of like "proof", I would prefer to keep the current names as they're consistent with those of cryptographic signing, and agnostic to any particular use case.

abandeali1

abandeali1 commented on Aug 5, 2018

@abandeali1

@frangio overloading the signature field should actually result in fewer bytes being passed around and should be cheaper, assuming that isValidSignature is being called by another contract (i.e the data is being copied to memory and is not included in the transaction calldata).

With bytes data, bytes signature, the ABI encoded bytes will look like: [dataOffset][signatureOffset][dataLength][data][signatureLength][signature], where everything is 32 bytes except for data and signature. This gives us a total of 128 + data.length + signature.length bytes.

With bytes32 hash, bytes signature, the ABI encoded bytes will look like: [hash][signatureOffset][signatureLength][signature]. This gives us a total of 96 + data.length + signature.length bytes (assuming that the signature field includes everything that would otherwise be in the data field).

So in the worst case scenario of using bytes32 hash, bytes signature, we save 32 bytes but add some extra hashing and complexity.

Now let's look that the case where we we use bytes data, bytes signature but data only contains a 32 bytes hash. The ABI encoded bytes are [dataOffset][signatureOffset][dataLength][hash][signatureLength][signature]. This means we add 64 bytes vs using bytes32 hash, bytes signature. There's an extra hidden cost here though - if calling isValidSignature from another contract, we now have to convert our 32 byte hash into a byte array before the call and then parse the hash out of byte array after the call. This isn't particularly easy without using some inline assembly (currently), and I'm guessing that the function will be used this way the majority of the time.

abandeali1

abandeali1 commented on Aug 5, 2018

@abandeali1

To be honest, the cost differences here are probably minimal. I think the main downside for the bytes data, bytes signature approach is converting a bytes32 to and from a byte array. With the bytes32 hash, bytes signature approach, the downside is confusion over potentially overloading signature. Which is better likely comes down to how often we think each case is likely to be used.

157 remaining items

radeksvarz

radeksvarz commented on Jul 18, 2023

@radeksvarz
Contributor

Is there any reference implementation for the ERC20 based tokens with permit function?
I assume there should be a separate function / ERC besides ERC2612's permit as that adjustement is challenged as discussed here: https://forum.openzeppelin.com/t/implementation-erc20-permit-eip-2612-compatible-with-eip-1271/8830
Is there any 1271 corresponding ERC to complement 2612 / 3009?

lukasz-glen

lukasz-glen commented on Jul 18, 2023

@lukasz-glen

Is there any reference implementation for the ERC20 based tokens with permit function?

What's wrong with this one?

Is there any 1271 corresponding ERC to complement 2612 / 3009?

What for actually? I was thinking about the same some time ago. I concluded that smart contract wallets (for instance) do not need permit, they should use regular approve. Even if gasless, it should be in ERC4337 style.

radeksvarz

radeksvarz commented on Jul 28, 2023

@radeksvarz
Contributor

Is there any reference implementation for the ERC20 based tokens with permit function?

What's wrong with this one?

OZ permit does not have ERC1271 validation. Apologies, my question should contain that 1271 context.

Is there any 1271 corresponding ERC to complement 2612 / 3009?

What for actually? I was thinking about the same some time ago. I concluded that smart contract wallets (for instance) do not need permit, they should use regular approve. Even if gasless, it should be in ERC4337 style.

I am also in doubt, but would like to read opinions on this matter.

In fact the question is whether the new ERC20 implementations should include ERC1271?

frangio

frangio commented on Jul 28, 2023

@frangio
Contributor

ERC-2612 (permit) can't be made fully ERC-1271 compatible because it receives an r,s,v representation of a signature, whereas ERC-1271 uses arbitrary length signatures. Additionally ERC-2612 explicitly says that ecrecover should be used to recover the address, which would rule out ERC-1271 in general, but arguably this is a technicality.

lukasz-glen

lukasz-glen commented on Jul 29, 2023

@lukasz-glen

I would change the perspective. What do we lose if 2612 and 3009 use r,s,v, not bytes signature and 1271?

3009 can be used to overcome approve/transferFrom problem. I assume that smartcontract wallets can execute approve and transferFrom within a single transaction (without 3009), still providing comparable gas savings.

2612 can be used in combination with 2771 to provide gasless tx. 3009 can be used to provide gasless tx also if I understand it correctly. But smartcontract wallets can provide gasless tx in a different way.

To be clear. I think that it would be better if 2612 and 3009 would have bytes signature and 1271 integration. But my point is that it would not be such gain as it seems.

xinbenlv

xinbenlv commented on Oct 6, 2023

@xinbenlv
Contributor

Hi @frangio and authors, EIP-1271 is a great piece of standard. I have a clarification question that I like to ask:

What's the rationale in choosing a bytes4 magicValue as opposed to

  1. use a different dataType such as bool or bytes32. If not using bool to prevent collision attack where attacker compose a function that collide with function selector and return value, wouldn't bytes4 be also too small?
  2. return zero instead of a 0xffffffff when not match?

Thank you!

PhABC

PhABC commented on Oct 6, 2023

@PhABC
ContributorAuthor

@xinbenlv

explicit bytes was chosen instead of a Boolean to decrease the risks of errors when validating, akin to type safety. It’s a common practice in case there were more than 2 types of messages, but also to better catch mock implementations. I believe it also makes it easier to distinguish and error from an invalid signature, but I forgot the nuances. Bytes32 is too big/ not worth it since we aren’t concerned with collisions here.

alextnetto

alextnetto commented on Feb 16, 2024

@alextnetto

@xinbenlv

explicit bytes was chosen instead of a Boolean to decrease the risks of errors when validating, akin to type safety. It’s a common practice in case there were more than 2 types of messages, but also to better catch mock implementations. I believe it also makes it easier to distinguish and error from an invalid signature, but I forgot the nuances. Bytes32 is too big/ not worth it since we aren’t concerned with collisions here.

Hey @PhABC, it is still not entirely clear for me what the difference between returning true or false and 0x1626ba7e or 0xffffffff (any other number would also mean false).

It's also more expensive to validate and deal with a bytes4 than a bool.

I appreciate the work put in here; remarkable EIP.

Amxx

Amxx commented on Feb 16, 2024

@Amxx
Contributor

Hey @PhABC, it is still not entirely clear for me what the difference between returning true or false and 0x1626ba7e or 0xffffffff (any other number would also mean false).

A contract that doesnot implemente ERC-1271 may have a fallback function, and that fallback function may return data.
From an caller's point of view, there is a risk the data return by the fallback is interpreted as a true boolean. This would cause the the isValidSignature lookup to appear as it succedded, even though it did not.

The whole poinr of 0x1626ba7e is that its very specific, and its unlikelly that another function (a fallback) would return that.

alextnetto

alextnetto commented on Feb 17, 2024

@alextnetto

Thanks a lot for the explanation @Amxx , make sense!

novaknole

novaknole commented on Feb 27, 2024

@novaknole

I actually liked the use case presented in the very first message of this thread about decentralized exchanges.

If decentralized exchange implements EIP1271 which is basically for the reason that smart contracts can also hold funds, then DEX smart contract after "isValidSignature" succeeds, also needs to call: token.transferFrom(maker, taker, value). Note that maker is smart contract now. How can that transfer succeed ? The only choice I can think of is asset contract(i.e maker) must be able to first approve the tokens to be spendable by exchange smart contract. This can only be done if maker smart contract includes the code of ".call" that can call any function externally(including "approve") on token.

I can't think of any other use case - if the maker contract doesn't have the possibility of "calling" external functions on other smart contracts beforehand, then use case the first message in this thread talks about is doomed. Am I wrong or I'm not seeing an better use case or I am right and that's it ?

PhABC

PhABC commented on Feb 27, 2024

@PhABC
ContributorAuthor

@novaknole Every single implementation of smart contract wallet (or account abstraction) uses ERC-1271(Gnosis safe, Sequence, Ambire, Pimlico, Immutable Passport, etc.). AA wallets don't have EOA signers like regular wallets, hence can only sign messages via ERC-1271 (and 6492)

The example outlined in ERC-1271 with exchanges was written well before AA became popular. If it were re-written today it would be centered around AA wallets.

radeksvarz

radeksvarz commented on Feb 28, 2024

@radeksvarz
Contributor

Is there any reference implementation for the ERC20 based tokens with permit function?

What's wrong with this one?

OZ permit does not have ERC1271 validation. Apologies, my question should contain that 1271 context.

Is there any 1271 corresponding ERC to complement 2612 / 3009?

What for actually? I was thinking about the same some time ago. I concluded that smart contract wallets (for instance) do not need permit, they should use regular approve. Even if gasless, it should be in ERC4337 style.

I am also in doubt, but would like to read opinions on this matter.

In fact the question is whether the new ERC20 implementations should include ERC1271?

No answer here, yet USDC updated its contracts to reflect ERC1271.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @dekz@marekkirejczyk@3esmit@frozeman@cag

        Issue actions

          ERC-1271 : Standard Signature Validation Method for Contracts · Issue #1271 · ethereum/EIPs