mirror of
https://github.com/status-im/EIPs.git
synced 2025-02-20 18:58:20 +00:00
* Added initial draft for an EIP regarding ENS text records. * Changed extended to vendor specific and added some keys. * Updated to new preamble header. * Initial Merkle-Airdrop EIP draft. * Added author Nick Johnsom and fixed typo. * Fixed typo. * Update and rename eip-draft-ens-text-data.md to eip-634.md * Removed Merkle Air Drops. * Removed draft for merkle air-drops. * Added preliminary draft for compact signatures. * Fixed type in test case. * Small changes. * Added Nick as a co-author, updated links with titles and expanded on Rationale. * Updated link title. * Updated with suggestions from @axic. * Updated EIP header. * Updated discussions-to URL. * Update EIPS/eip-2098.md Updated bracket format for GitHub username. Co-Authored-By: Alex Beregszaszi <alex@rtfs.hu> * Update EIPS/eip-2098.md Fixed typo. Co-Authored-By: Alex Beregszaszi <alex@rtfs.hu> * Update EIPS/eip-2098.md Fixed typo. Co-Authored-By: Alex Beregszaszi <alex@rtfs.hu> * Update EIPS/eip-2098.md Fixed typo. Co-Authored-By: Alex Beregszaszi <alex@rtfs.hu> * Update EIPS/eip-2098.md Fixed typo. Co-Authored-By: Alex Beregszaszi <alex@rtfs.hu> * Update EIPS/eip-2098.md Fixed typo. Co-Authored-By: Alex Beregszaszi <alex@rtfs.hu> * Added link to ethers implmenetation of splitSignature which derives the compact vs representation. * Add explicit bit layout of the compact representation. * Moved links to the original idea to a separate Acknowledgments section. * Update EIPS/eip-2098.md Fixed typo. Co-Authored-By: Alex Beregszaszi <alex@rtfs.hu>
220 lines
7.2 KiB
Markdown
220 lines
7.2 KiB
Markdown
---
|
|
eip: 2098
|
|
title: Compact Signature Representation
|
|
status: Draft
|
|
type: Informational
|
|
author: Richard Moore (@ricmoo), Nick Johnson <nick@ethereum.org>
|
|
discussions-to: https://github.com/ethereum/EIPs/issues/2440
|
|
created: 2019-03-14
|
|
---
|
|
|
|
## Simple Summary
|
|
|
|
This proposal describes a compact representation of an Ethereum Signature.
|
|
|
|
|
|
## Abstract
|
|
|
|
The secp256k1 curve permits the computation of the public key of signed
|
|
digest when coupled with a signature, which is used implicitly to
|
|
establish the origin of a transaction from an Externally Owned Account
|
|
as well as on-chain in EVM contracts for example, in meta-transactions and
|
|
multi-sig contracts.
|
|
|
|
Currently signatures require 65 bytes to represent, which when aligned
|
|
to 256-bit words, requires 96 bytes (with 31 zero bytes injected). With
|
|
compact signatures, this can be reduced to 64 bytes, which remains 64
|
|
bytes when word-aligned.
|
|
|
|
|
|
## Motivation
|
|
|
|
The motivations for a compact representation are to simplify handling
|
|
transactions in client code, reduce gas costs and reduce transaction sizes.
|
|
|
|
|
|
## Specification
|
|
|
|
A secp256k1 signature is made up of 3 parameters, `r`, `s` and `v`. The `r`
|
|
represents the `x` component on the curve (from which the `y` can be
|
|
computed), and the `s` represents the challenge solution for signing by a
|
|
private key. Due to the symmetric nature of an elliptic curve, a `v` is
|
|
required, which indicates which of the 2 possible solutions was intended,
|
|
by indicating its parity (odd-ness).
|
|
|
|
Two key observations are required to create a compact representation.
|
|
|
|
First, the `v` parameter is always either 0 or 1 (canonically the values used
|
|
have been 27 and 28, as these values didn't collide with other binary prefixes
|
|
used in Bitcoin.)
|
|
|
|
Second, the top bit of the `s` parameters is **always** 0, due to the use of
|
|
canonical signatures which flip the solution parity to prevent negative values,
|
|
which was introduced as [a constraint in Homestead](http://eips.ethereum.org/EIPS/eip-2).
|
|
|
|
So, we can hijack the top bit in the `s` parameter to store the value of `v`, resulting in:
|
|
|
|
```
|
|
[256-bit r value][1-bit v value][255-bit s value]
|
|
```
|
|
|
|
|
|
### Example Implementation In Python
|
|
|
|
```python
|
|
def to_compact(r, s, v):
|
|
return {
|
|
"r": r,
|
|
"vs": ((v - 27) << 255) | s
|
|
}
|
|
|
|
def to_canonical(r, vs):
|
|
return {
|
|
"r": r,
|
|
"s": vs & ((1 << 255) - 1),
|
|
"v": (27 + (vs >> 255))
|
|
}
|
|
```
|
|
|
|
|
|
## Rationale
|
|
|
|
The compact representation proposed is simple to both compose and decompose
|
|
in clients and in Solidity, so that it can be easily (and intuitively) supported,
|
|
while reducing transaction sizes and gas costs.
|
|
|
|
|
|
## Backwards Compatibility
|
|
|
|
The Compact Representation does not collide with canonical signature as
|
|
it uses 2 parameters (r, vs) while canonical signatures involve 3
|
|
separate parameters (r, s, v).
|
|
|
|
|
|
## Test Vectors
|
|
|
|
```
|
|
Private Key: 0x1234567890123456789012345678901234567890123456789012345678901234
|
|
Message: "Hello World"
|
|
Signature:
|
|
r: 0x68a020a209d3d56c46f38cc50a33f704f4a9a10a59377f8dd762ac66910e9b90
|
|
s: 0x7e865ad05c4035ab5792787d4a0297a43617ae897930a6fe4d822b8faea52064
|
|
v: 27
|
|
Compact Signature:
|
|
r: 0x68a020a209d3d56c46f38cc50a33f704f4a9a10a59377f8dd762ac66910e9b90
|
|
vs: 0x7e865ad05c4035ab5792787d4a0297a43617ae897930a6fe4d822b8faea52064
|
|
```
|
|
|
|
```
|
|
Private Key: 0x1234567890123456789012345678901234567890123456789012345678901234
|
|
Message: "It's a small(er) world"
|
|
Signature:
|
|
r: 0x9328da16089fcba9bececa81663203989f2df5fe1faa6291a45381c81bd17f76
|
|
s: 0x139c6d6b623b42da56557e5e734a43dc83345ddfadec52cbe24d0cc64f550793
|
|
v: 28
|
|
Compact Signature:
|
|
r: 0x9328da16089fcba9bececa81663203989f2df5fe1faa6291a45381c81bd17f76
|
|
vs: 0x939c6d6b623b42da56557e5e734a43dc83345ddfadec52cbe24d0cc64f550793
|
|
```
|
|
|
|
|
|
## Gas Analysis
|
|
|
|
**Solidity**
|
|
|
|
```
|
|
// See: https://ropsten.etherscan.io/address/0xce826fbc499e3723df5668ea90a4bc51aeca13b8
|
|
|
|
pragma solidity 0.5.6;
|
|
|
|
contract TestCompact {
|
|
address _lastAddr;
|
|
|
|
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) public returns (address) {
|
|
_lastAddr = ecrecover(hash, v, r, s);
|
|
}
|
|
|
|
function recoverCompact(bytes32 hash, bytes32 r, bytes32 vs) public returns (address) {
|
|
bytes32 s = vs & 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
|
|
uint8 v = 27 + uint8(uint256(vs) >> 255);
|
|
_lastAddr = ecrecover(hash, v, r, s);
|
|
}
|
|
|
|
function lastAddr() public view returns (address) {
|
|
return _lastAddr;
|
|
}
|
|
}
|
|
```
|
|
|
|
**JavaScript**
|
|
|
|
```
|
|
let address = "0xcE826fbC499e3723DF5668Ea90a4BC51AeCa13b8";
|
|
let abi = [
|
|
'function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) public returns (address)',
|
|
'function recoverCompact(bytes32 hash, bytes32 r, bytes32 vs) public returns (address)',
|
|
'function lastAddr() public view returns (address)'
|
|
];
|
|
let wallet = Wallet.fromMnemonic("relief cousin sorry cabin burst frog slush flavor pitch tragic hip disagree").connect(provider);
|
|
|
|
let hash = "0x6ac9e083f46825a57816e023ae641ee45ac9b82cc2c370c10ec1ef4c835fa182";
|
|
let sig = wallet.signingKey.signDigest(hash)
|
|
|
|
let contract = new Contract(address, abi, wallet);
|
|
(async function() {
|
|
let tx, receipt;
|
|
|
|
console.log("Canonical:");
|
|
tx = await contract.recover(hash, sig.v, sig.r, sig.s);
|
|
console.log(" Hash: ", tx.hash);
|
|
console.log(" Data: ", tx.data);
|
|
console.log(" Length: ", utils.hexDataLength(tx.data));
|
|
receipt = await tx.wait();
|
|
console.log(" Gas Used:", receipt.gasUsed.toString());
|
|
|
|
console.log("Compact:");
|
|
tx = await contract.recoverCompact(hash, sig.r, sig._vs);
|
|
console.log(" Hash: ", tx.hash);
|
|
console.log(" Data: ", tx.data);
|
|
console.log(" Length: ", utils.hexDataLength(tx.data));
|
|
receipt = await tx.wait();
|
|
console.log(" Gas Used:", receipt.gasUsed.toString());
|
|
})();
|
|
```
|
|
|
|
|
|
**Result**
|
|
|
|
```
|
|
/home/ricmoo> ethers --network ropsten run test.js
|
|
Canonical:
|
|
Hash: 0x3280985011d2f25e76141681e865216e796cf065880eb9dd1f8221f3243028d2
|
|
Data: 0xc2bf17b06ac9e083f46825a57816e023ae641ee45ac9b82cc2c370c10ec1ef4c835fa182000000000000000000000000000000000000000000000000000000000000001be95b1a8633ee7ff851f68cf4303030b1e2596d686cafd9803cef74919a9139291c492d05696da754cf342bec961d00d9f7dfac3ab90b1e9352177c7b9bfa2a5d
|
|
Length: 132
|
|
Gas Used: 37541
|
|
Compact:
|
|
Hash: 0x513bd8bd8710a84903235ca60591fc60f2f3db524d14cc1b6874edc628512176
|
|
Data: 0x1230abb66ac9e083f46825a57816e023ae641ee45ac9b82cc2c370c10ec1ef4c835fa182e95b1a8633ee7ff851f68cf4303030b1e2596d686cafd9803cef74919a9139291c492d05696da754cf342bec961d00d9f7dfac3ab90b1e9352177c7b9bfa2a5d
|
|
Length: 100
|
|
Gas Used: 37329
|
|
```
|
|
|
|
|
|
## Implementations
|
|
|
|
The ethers.js library [supports this in v5](https://github.com/ethers-io/ethers.js/blob/ethers-v5-beta/packages/bytes/src.ts/index.ts#L323)
|
|
as an unofficial property of split signatures (i.e. `sig._vs`), but should be
|
|
considered an internal property that may change at discretion of the community
|
|
and any changes to this EIP.
|
|
|
|
|
|
## Acknowledgments
|
|
|
|
- [Original Tweet from Nick and conversation](https://twitter.com/nicksdjohnson/status/1030830279487709185)
|
|
- [Original Solidity Inspiration](https://github.com/HarryR/solcrypto/blob/01a3c5d91053f3b8bffde328146d5f18015ebfed/contracts/ECDSA.sol#L6)
|
|
|
|
|
|
## Copyright
|
|
|
|
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
|