Skip to content
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

NEP-483: Prefix Tag for Signed Messages #483

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

gagdiez
Copy link
Contributor

@gagdiez gagdiez commented May 24, 2023

A proposal to include a standardized prefix in all signed messages that are not transactions.

Particularly, we propose to prepend the prefix $2^{30}$ + NEP-number actionable messages (i.e. those that are processed on-chain), and the prefix $2^{31}$ + NEP-number to all non-actionable messages (i.e. those destined for off-chain use).


NEP Status (Updated by NEP moderators)

SME reviews:

Voting indications:

Protocol Work Group voting has not started yet.

@gagdiez gagdiez requested a review from a team as a code owner May 24, 2023 09:16
@gagdiez gagdiez changed the title NEP-461: Prefix Tag for Signed Messages NEP-483: Prefix Tag for Signed Messages May 24, 2023
@gagdiez
Copy link
Contributor Author

gagdiez commented May 24, 2023

A revival of #461 which I mistakenly closed.

Maybe this is great moment to get back in the move, since the previous PR was stagnated for two months. I know that making NEPs is a slow process, but it is always highly rewarding!

cc 1. @robin-near @Ekleog-NEAR @mfornet @jakmeier @firatNEAR @abacabadabacaba you were all part of the conversation.

cc 2. @frol @bowenwang1996 @DavidM-D @robin-near you were part of the committee appointed to this PR.

Including a large `u32` prefix allows to easily distinguish transactions from messages, ensuring that they never overlap.

```ts
sha256.hash(Borsh.serialize<u32>(large-number) + Borsh.serialize(MessageStructure))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

original comment by @mfornet

Before reading the suggestion, I thought about the user signing a message of this type:

Suggested change
sha256.hash(Borsh.serialize<u32>(large-number) + Borsh.serialize(MessageStructure))
Borsh.serialize<u32>(large-number) + sha256.hash(Borsh.serialize(MessageStructure))

It is 4 bytes longer, but the advantage is that the wallet (signing device) can tell apart the target (i.e transaction/on-chain/off-chain) by just looking at this payload, rather than requiring the whole message. It is probably not safe anyway to sign a payload without seeing the message, but it might make sense for low-end devices (like some hardware wallets) where having the whole message is expensive or not possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mfornet wouldn't this have the problem that sha256(transaction) could collide with borsh(prefix) + sha256(MessageStruture)? If all transactions are of the form sha256(prefix + transaction) then by definition there cannot be a collision.


We will need to update [NEP366](https://github.com/near/NEPs/blob/master/neps/nep-0366.md) to follow this standard, and make sure all future NEPs follow it, which might be hard to track.

If at any point there is an update on how transactions are encoded, this NEP could stop being valid.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

original comment by @mfornet

If any protocol change updates the tx encoding, we should enforce that the first 4 bytes are used following this NEP. (i.e., they must be decoded as a number in the range [0, 2**30).

What do you think the best way is to be alert of this? (Putting some comments in the client source code?)

Copy link
Contributor Author

@gagdiez gagdiez May 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

original comment by @mfornet

I propose introducing a "protocol update" that enforces the first four bytes of every "classic tx" to be in the proper range. This is the case today (implicitly), but we should add it to the spec, and an extra assertion to the client (no real protocol change required) so we don't break this in the future.

| - | - |
| $< 2^{30}$ | [Classic Transactions](https://nomicon.io/RuntimeSpec/Transactions) |
| $2^{30}$ + NEP-number | On-chain Actionable Message, e.g. [`SignedDelegateAction`](https://github.com/near/NEPs/blob/master/neps/nep-0366.md) |
| $2^{31}$ + NEP-number | Off-chain Message, e.g. [`signMessage`](https://github.com/near/NEPs/pull/413) |
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

original comment by @mfornet

Should we explicitly reserve the range [3 * 2^30, 4 * 2^30) as unused / experimentation / undefined?

@gagdiez
Copy link
Contributor Author

gagdiez commented May 24, 2023

The last comments were involved on the important discussion of IANA vs NEP-prefix on tagging the transactions. I will try to summarise the discussion here:

The current proposal is to implement a message prefix in the form:
sha256( borsh(NEP-number) + borsh(MessageStructure) )

@Ekleog-NEAR proposed to use the IANA approach, since that would allow to simply make if discriminant == standard, and one NEP could handle multiple prefixes.

discriminant + sha256( borsh(MessageStructure) )

@abacabadabacaba and @jakmeier raised security concerns, since the discriminant could potentially be encoded in a way that collides with other transactions, and plead for keeping the proposal as it is.

@mfornet was proposing to stick with the NEP-prefix, but in the form of a non-serialized prefix:

borsh(NEP-number) + sha256( borsh(MessageStructure) )

proposal  pros cons
IANA  simpler to read we would need to have a way to centralise and communicate the IANA prefixes
encoded(NEP+message)  straightforward prefix need to decode first 4 bytes to check the message type
NEP+encode(message)  straightforward prefix, no need to decode first 4 bytes adds 4 bytes of information

I personally think that we can keep the current proposal unmodified since:

  • NEPs should present only one type of message, if multiple are present, then that signals the need for more NEPs (e.g. it has not been a problem so far in building NFT / FTs as multiple NEPs)
  • Adding the NEP + encode(message) would make the transaction more human readable, but on the actual implementation has little impact since anyway you need to "check first four bytes and act on them", one pass of deserializing 4 bytes does not save significative computational resources (i.e. time, processing power).

@jakmeier
Copy link
Contributor

Noteworthy that we already had to pick something when implementing delegate actions to enable meta transactions.
There we went with "the current proposal". But to be more precise, here is exactly what we implemented.

To sign a message, we use discriminant(nep) as a prefix, where the discriminant in constructed as described in the main comment of this PR (add 2^30 or 2^31 to the NEP number).

From that, we define a new signature method, let's call it sign_nep483.

sign_nep483(msg, nep) := sign(sha256(discriminant(nep) ++ borsh(msg)))

sign refers to an ed25519 or secp256k1 signature.

I think so far this is exactly what the "current proposal" is. But we had to make one more decision, which is aobut what to send across the wire. We went with this:

SignedMessage {
    signature := sign_nep483(msg, nep),
    msg := msg,
}

Note that msg is without the prefix. This means the actual prefix on the wire is only the signature. The receiver has to know (or guess) the NEP to verify the signature, as it is not included in the transmitted message.

I believe this makes sense because the purpose of the prefix is to avoid encoding collision attacks. It it not meant as a way to give types to messages, that IMO should happen independently of this NEP.

You may find the full implementation as it is currently running in mainnet here: https://github.com/near/nearcore/blob/489b9767c38eef28d6f467846f8f938e602ecc94/core/primitives/src/signable_message.rs

@frol frol added WG-protocol Protocol Standards Work Group should be accountable A-NEP A NEAR Enhancement Proposal (NEP). S-review/needs-sme-review A NEP in the REVIEW stage is waiting for Subject Matter Expert review. labels May 24, 2023
@frol frol requested review from a team and bowenwang1996 May 24, 2023 20:15
@frol
Copy link
Collaborator

frol commented May 24, 2023

you were part of the committee appointed to this PR.

This NEP needs to be reviewed by @near/wg-protocol work group. I am only playing a moderator role here.

@gagdiez
Copy link
Contributor Author

gagdiez commented May 25, 2023

sign_nep483(msg, nep) := sign(sha256(discriminant(nep) ++ borsh(msg)))

@jakmeier indeed, this is exactly the "current proposal", glad to see the NEP already being implemented <3 , we now wait for the @near/wg-protocol work group to give their verdict.

Of course, meanwhile the community can keep commenting and working on the NEP if any major concerns arise.

@bowenwang1996
Copy link
Collaborator

bowenwang1996 commented May 29, 2023

As a working group member, I nominate @Ekleog and @abacabadabacaba as subject matter experts to review this NEP.

Technical Review Guidelines
  • First, review the proposal within one week. If you have any suggestions that could be fixed, leave them as comments to the author. It may take a couple of iterations to resolve any open comments.
  • Second, once all the suggestions are addressed, produce a Technical Summary, which helps the working group members make a weighted decision faster. Without the summary, the working group will have to read the whole discussion and potentially miss some details.
    Technical Summary guidelines:
    • A recommendation for the working group if the NEP is ready for voting (it could be approving or rejecting recommendation). Please note that this is the reviewer's personal recommendation.
    • A summary of benefits that surfaced in previous discussions. This should include a concise list of all the benefits that others raised, not just the ones that the reviewer personally agrees with.
    • A summary of concerns or blockers, along with their current status and resolution. Again, this should reflect the collective view of all commenters, not just the reviewer's perspective.
      Here is a nice example and a template for your convenience:
### Recommendation
Add recommendation
### Benefits
* Benefit
* Benefit
### Concerns
| # | Concern | Resolution | Status |
| - | - | - | - |
| 1 | Concern | Resolution | Status |
| 2 | Concern | Resolution | Status |

Please tag the @near/nep-moderators once you are done, so we can move this NEP to the voting stage. Thanks again.

@DavidM-D
Copy link

DavidM-D commented Sep 7, 2023

I approve this NEP

@walnut-the-cat
Copy link
Contributor

Hello, NEP moderator here.

@Ekleog and @abacabadabacaba , are you still the right SMEs to review this NEP?

@gagdiez , could you confirm if this project is still ongoing?

@gagdiez
Copy link
Contributor Author

gagdiez commented Nov 20, 2023

@walnut-the-cat as far as I know it has even been implemented for some NEPs (particularly, meta-transactions), so yes, this is still valid and worth reviewing

@Ekleog-NEAR
Copy link
Contributor

Oh right I totally forgot about this, thank you. I won’t have the time to review again today, and will be away the rest of the week, but I can try to review early next week.

That said, TBH considering all the work on this was already landed and is already part of our protocol, I’m not sure it makes sense to finish the NEP review process with this one. I still have the same concerns I had given on NEP-461, about the currently-written layer mix between NEP number (which is an internal detail of our specification process) and message tag. Incidentally, I cannot find the message where @jakmeier or @abacabadabacaba have raised the security concerns listed in the summary above, and I do not understand what they would be.

But what would be the point of raising this again now, considering all the work on this had already landed three months before I was tagged as a NEP reviewer?

So I’d argue in favor of bypassing the NEP process here, acknowledging that we messed up by landing the code (long) before reaching an agreement on what the NEP contents should be, and landing this PR to document the implementation choices we made.

@walnut-the-cat WDYT?

@jakmeier
Copy link
Contributor

So I’d argue in favor of bypassing the NEP process here, acknowledging that we messed up by landing the code (long) before reaching an agreement on what the NEP contents should be, and landing this PR to document the implementation choices we made.

I don't think landing meta transaction is the same as deciding on a standard for NEP-483.

I admit, the timing of events was bad here.
But it's not necessarily wrong to solve a problem for a specific feature (NEP-461) first and then later decide on a generally acceptable standard.

The purpose of NEP-483 is, as I understand it, to agree on how contextually bound signatures should be implemented for future cases, inside and outside the core protocol. Meta-transaction needed to solve one possible conflict of a key being shared for signature in different contexts and hence we came up with a solution before the standard was ready.

As more things are signed with user account keys, there are more and more potential conflicts. Thus, I can see value in formally reviewing and working out the details for a standardized solution. Having meta transaction already shipped can even be a useful precedent to understand practical implications of this particular choice.

All that said, I think we also need to think about priority of different proposals. I don't know how high it is for this specific proposal, given that nobody really seems to be waiting for a resolution on it right now.

@walnut-the-cat
Copy link
Contributor

@Ekleog-NEAR and @abacabadabacaba , if you agree with what @jakmeier says, let's have another look at the NEP and see what we can align on. Please focus on general applicability than specific usecase when reviewing the NEP.

Let's target December 6th as the new review completion date.

@Ekleog-NEAR
Copy link
Contributor

Before handing in my technical report, I have one comment: I do not think this numbering scheme is good, because:

  1. It uses the whole space of remaining message IDs, thus making it impossible to extend later on
  2. It does not allow one NEP to define two new message types
  3. The separation in IDs between on-chain and off-chain is not, as far as I can tell, motivated

Currently, there is a single message type that we can no longer change: on-chain NEP 366, ie. 2**30 + 366.

As such, I suggest changing the numbering scheme to be (B = 2**30):

  • [B, B + 2**20): Space reserved for the NEP number. Each NEP can use without any process the message types [B + NEP << 4, B + (NEP + 1) << 4), thus allowing each NEP up to 16 message types without any process, for NEP numbers up to 2**16
  • [B + 2**20, B + 2**21): Space reserved for experimentation. Numbers here MUST NOT be used by any production system
  • [B + 2**21, 2**32): Reserved for future use, eg. if one NEP ever were to require more than 16 message types.

The only issue will be backward compatibility with NEP 366. If we allowed a single message type for each NEP, it’d be easy, so this is one solution. Another would be to use the numbering scheme I suggest, and just have NEP 366 use a number from the region of NEP 22 (= 366 // 16), which we know doesn’t use any anyway.

WDYT?

@walnut-the-cat
Copy link
Contributor

cc. @gagdiez for @Ekleog-NEAR 's comment

@walnut-the-cat
Copy link
Contributor

@gagdiez , happy new year. Would like to resurface this per @Ekleog-NEAR 's comment and want to hear your thoughts.

@gagdiez
Copy link
Contributor Author

gagdiez commented Jan 8, 2024

I am not necessarily opposed to what @Ekleog-NEAR is proposing, but I would like to point that it would already conflict with two NEPs (and thus 3 types of signed message) at least:

Touching on other points:

@Ekleog-NEAR I assume the difference between 2**30 and 2**31 was to make very simple to discard messages that were NOT supposed to be relayed to the blockchain based on the prefix.

@Ekleog-NEAR I remember there was a discussion on how many messages an NEP can have, and that maybe an NEP with multiple messages was indeed multiple NEPs.


Now I would like to remember everyone that I created this document after the protocol team requested help writing the NEP. With this, I mean that I do not have strong opinions on the exact ranges the messages should live in, or even if the "message prefixes" should be standardized.

It seams, given the amount of time this PR has been opened - and the diverse opinions that different team members present - that the protocol team cannot reach consensus on the idea... at least not through this async channel. Thus, I encourage the moderators to reach all important parties and take the discussion to a live and synchronous channel (e.g. a videocall) and reach out to me to keep helping with the NEP writing.

@Ekleog-NEAR
Copy link
Contributor

Thank you for your answer, I hadn’t known of NEP-413! And this one is actually problematic, as it uses 2^31 + 413 as a prefix tag, which is not the one of any other low-number NEP. Once more, I’m very sad that we built upon this NEP without first seeing it to completion, because now we have a fractured NEP range that makes it hard to have a good numbering scheme :’(

As for easily discarding messages that are not one of the known types, we’ll have to match on the message type anyway to know what to do with the message, so I don’t really see a point in doing so.

This being said, here is a new numbering proposal, that should keep working with NEP-413 while still avoiding the forward-compatibility issue that with the current definition we’re burning the whole available NEP range:

  • [0, 2**30): Regular transactions
  • [_, 2**30 + 2**22): 2**18 blocks of 2**4 numbers, allowing for up to 262144 NEPs of up to 4 message types
  • [_, 2**31): Space reserved for future use
  • [_, 2**31 + 2**20): Space reserved for manual allocation of message types for NEPs that’d need more than 4, NEP-413 would fall here
  • [_, 2**32): Space reserved for future use

WDYT?

Also, to answer @gagdiez’s comment, does anyone know who had defined the previous scheme, as it seems like they might object to what I’m suggesting? All I know of is the discussion in #461, and I don’t think any objection to a scheme like this was raised there, there were just objections to the "we are IANA" approach for developer velocity

@walnut-the-cat
Copy link
Contributor

@gagdiez, any thoughs on @Ekleog-NEAR 's proposal?

@victorchimakanu
Copy link

Hello @gagdiez @Ekleog-NEAR , I'd like to confirm the current status of this NEP?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-NEP A NEAR Enhancement Proposal (NEP). S-review/needs-sme-review A NEP in the REVIEW stage is waiting for Subject Matter Expert review. WG-protocol Protocol Standards Work Group should be accountable
Projects
Status: REVIEW
Development

Successfully merging this pull request may close these issues.

None yet

8 participants