Bitcoin programmer Gregory Maxwell states the following on Reddit:
There exists a flaw in the design of the Bitcoin protocol where it is feasible for an outside party to take a legitimate transaction of yours and alter it in such a way that it remains valid and functionally the same yet has a distinct transaction ID. This significantly complicates the creation of correct wallet software, and it can be exploited to invalidate lengthy sequences of unconfirmed transactions that rely on the original transaction (as transactions reference each other by txid).
This problem emerges from multiple origins, one being OpenSSL’s readiness to accept and interpret signatures with invalid encodings. A standard ECDSA signature encodes two large integers, and the encoding isn’t of constant length— if leading zeros are present, they are expected to be removed.
It’s simple to create software that presumes the signature will be of a constant length, thus leaving unnecessary leading zeros present.
This serves as a very intriguing cautionary narrative, and it is particularly crucial because scenarios like these are part of the rationale behind certain design choices we’ve made in our development philosophy. The specific issue is this: numerous individuals continue to highlight that we are, in many instances, unnecessarily recreating existing solutions, designing our own serialization format, RLP, instead of utilizing the available protobuf, and we’re developing a scripting language specific to applications rather than “simply using Lua.” This concern is genuinely valid; the not-invented-here syndrome is a commonly-used term of derision, which means that engaging in such internal development necessitates justification.
The cautionary tale I referenced earlier offers precisely the ideal example for the justification I will present. External technologies, such as protobuf, Lua, or OpenSSL, are quite robust and have years of development support, but in numerous instances, they were never designed with the perfect consensus, determinism, and cryptographic integrity that cryptocurrencies demand. The OpenSSL scenario mentioned above exemplifies this perfectly; aside from cryptocurrencies, there are virtually no other circumstances where the ability to convert a valid signature into another valid signature with a different hash poses a significant issue, yet here it is catastrophic. One of our fundamental principles in Ethereum is simplicity; the protocol ought to be as straightforward as possible, and it should not include any black boxes. Every single feature of every sub-protocol should be thoroughly documented either in the whitepaper or on the wiki, and developed based on that as a specification (i.e., test-driven development). Accomplishing this for an existing software package is arguably nearly as challenging as constructing an entirely new package from the ground up; in fact, it might even be harder, as existing software often includes more complications than necessary to be feature-complete, while our alternatives do not – refer to the protobuf specification and contrast it with the RLP specification to grasp what I mean.
It is important to note that the principle outlined above has its boundaries. For instance, we are certainly not naive enough to attempt to create our own hashing algorithms, instead opting for the universally recognized and well-tested SHA3, and for signatures, we’re utilizing the same well-known secp256k1 as Bitcoin, although we’re employing RLP to store the v,r,s triple (with v being an additional two bits for public key recovery) rather than the OpenSSL buffer protocol. These kinds of scenarios are precisely where “simply using X” is the most appropriate approach, because X has a clear and well-understood interface and there are no subtle distinctions among various implementations. The SHA3 of the empty string is c5d2460186…a470 in C++, in Python, and in Javascript; there’s no dispute about it. Between these two extremes, the primary task is to strike the right equilibrium.