-
Notifications
You must be signed in to change notification settings - Fork 146
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
disputes: attestation update to latest format #188
Conversation
bytes32 hash; | ||
uint16 hashFunction; | ||
// Attestation data | ||
struct Attestation { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was called Receipt
in the latest RFC. The name of the struct factors into the hashKey of EIP-712, so we need to have consistency. I don't mind if we switch to Attestation
and SignedAttestation
if you prefer, but we need to choose just 1.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use the same as in the RFC.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The latest here is confusing and differs from the RFC. There is an Attestation
struct, which matches the RFC I think. But then, in this file there is the ATTESTATION_TYPE_HASH
which holds the schema Attestation(bytes32 requestCID,bytes32 responseCID,bytes32 subgraphID)
. This doesn't match the Attestation
struct, but has the fields of the Receipt
struct and the name of the Attestation
struct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@That3Percent this is the one you mention right?
struct Receipt {
hash requestCID,
hash responseCID,
hash subgraphID,
}
So the type_hash says ATTESTATION when it should say RECEIPT as the type hash does not include the signature.
You are right I will do that change!
For the struct I used Attestation because that is what I parse from the incoming transaction, the receipt + signature = attestation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And the type hash should be ""Receipt(bytes32 requestCID,bytes32 responseCID,bytes32 subgraphID)" @That3Percent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think that the type hash should be "Receipt(...
, but also that the variable name should be renamed to RECEIPT_TYPE_HASH
so as not to be confused with the Attestation
struct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 updated in the last commit
contracts/DisputeManager.sol
Outdated
bytes32 s; | ||
struct SignedAttestation { | ||
Attestation data; | ||
bytes sig; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This also differs from the details in the latest RFC. I had copied the details from here, before these changes. So instead of bytes
sig (which has variable length), we have fields for v
r
and s
in the RFC.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm unpacking v,r,s into the struct now.
contracts/DisputeManager.sol
Outdated
bytes32 r; | ||
bytes32 s; | ||
struct SignedAttestation { | ||
Attestation data; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The RFC does not nest the structs. I'm amenable to nesting, so long as it doesn't increase the size of the struct due to the padding rules. As far as I can tell, we're in the clear here since it looks like the Attestation
type takes up exactly 3 storage slots - but I'm no solidity expert so you'll want to double-check. In any case, we need to have consistency so let me know if you want me to update the RFC.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated structs to match the RFC.
contracts/DisputeManager.sol
Outdated
} | ||
|
||
uint256 private constant ATTESTATION_SIZE_BYTES = 192; | ||
// Includes attestation + signature (96 + 65) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is wrong (or misplaced). I think you mean to put it over PAYLOAD_SIZE_BYTES
); | ||
bytes32 private constant DOMAIN_NAME_HASH = keccak256("Graph Protocol"); | ||
bytes32 private constant DOMAIN_VERSION_HASH = keccak256("0.1"); | ||
bytes32 private constant DOMAIN_VERSION_HASH = keccak256("0"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Sorry, GitHub wouldn't let me comment on line 174). The domainSeparator
calculation seems wrong to me. Note that in the EIP domainSeparator is hashStruct(eip712Domain)
but within the EIP712Domain
struct the version
field is of type string
, not bytes32
. So, pre-hashing this value is not correct. Same for the name
field.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
string is variable length and bytes32 is fixed, I think you found an issue, I'll review it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using variable-length values in EIP712Domain
should not be an issue, since the domainSeparator
is meant to be pre-calcuated and always has an output size of bytes32
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been reviewing the EIP712 and found an example on how the eip712 domain is created. They hash all string data.
https://github.com/ethereum/EIPs/blob/master/assets/eip-712/Example.sol
function hash(EIP712Domain eip712Domain) internal pure returns (bytes32) {
return keccak256(abi.encode(
EIP712DOMAIN_TYPEHASH,
keccak256(bytes(eip712Domain.name)),
keccak256(bytes(eip712Domain.version)),
eip712Domain.chainId,
eip712Domain.verifyingContract
));
}
In the code I'm doing:
// EIP-712 domain separator
DOMAIN_SEPARATOR = keccak256(
abi.encode(
DOMAIN_TYPE_HASH,
DOMAIN_NAME_HASH,
DOMAIN_VERSION_HASH,
_getChainID(),
address(this),
DOMAIN_SALT
)
);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, you are correct in pre-hashing the strings. I reviewed encodeData
to see if there was any explanation for this and there is:
Each encoded member value is exactly 32-byte long.
...and then later...
The dynamic values bytes and string are encoded as a keccak256 hash of their contents.
contracts/DisputeManager.sol
Outdated
require(_payload.length == PAYLOAD_SIZE_BYTES, "Signed attestation must be 161 bytes long"); | ||
|
||
// Decode attestation | ||
bytes32 requestCID = _payload.slice(0, 32).toBytes32(0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does solidity seriously not have some utility for this? You have to do this by hand every time?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Solidity is super limited, we are using a BytesLib library to do bytes manipulation.
I have an item in my internal TODO to see if we can use a newer feature from Solidity 0.6 (the original code was written for Solidity 0.5.0) to slice calldata bytes
. https://solidity.readthedocs.io/en/v0.6.0/types.html#array-slices
Will give quick look before we merge this PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm now using a feature from Solidity 0.6 to slice arrays and removed a dependency we had on a BytesLib.
9746d13
to
b1bec82
Compare
2e7c7fc
to
165852f
Compare
b1bec82
to
a66a0f0
Compare
contracts/DisputeManager.sol
Outdated
|
||
// Decode attestation | ||
bytes memory attestation = _data.slice(32, ATTESTATION_SIZE_BYTES); | ||
require(attestation.length == ATTESTATION_SIZE_BYTES, "Signature must be 192 bytes long"); | ||
bytes32 requestCID = abi.decode(_data[0:32], (bytes32)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. I wasn't clear on my question before about what APIs are offered by solidity. What I want to know is whether this kind of manual deserialization is necessary. Is there not some sort of transmute/cast from bytes
to Attestation
? Or is it always necessary for solidity developers to roll their own deserialization all the time?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I misunderstood your initial comment but it helped me to rewrite it in a more efficient way.
You need to roll your own deserialization, it would be great to cast from bytes to the struct directly. The closest discussion about that I found is this one ethereum/solidity#7134
contracts/DisputeManager.sol
Outdated
_attestation.responseCID, | ||
_attestation.subgraphID | ||
); | ||
bytes32 disputeID = getDisputeID(receipt); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am concerned that this uniquely identifies a dispute from only these fields, and does not include the indexNode in the hash in any way. Since multiple indexers may provide the same incorrect responses, separate disputes need to be created for them.
If anything, the disputeId
should be created from r
, s
, and v
once all other checks have passed since these uniquely identify the request/response/indexer tuple.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good observation. Yes, this code is now rejecting when two receipts are similar and are submitted by different indexers. I'll ask Brandon as he had some thoughts about conflicting attestations.
contracts/DisputeManager.sol
Outdated
_attestation.subgraphID, | ||
_attestation.r, | ||
_attestation.s, | ||
_attestation.v |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The field order here does not match the Attestation
struct. It has v,r,s
but this has r,s,v
. Is that a problem?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use:
struct Attestation {
bytes32 requestCID;
bytes32 responseCID;
bytes32 subgraphID;
bytes32 r;
bytes32 s;
uint8 v;
}
The libraries used to sign (web3, ethers) send the signature like r,s,v
test/lib/attestation.js
Outdated
) // IEP712 : domain separator + signed attestation | ||
receipt.substring(2) + // Attestation | ||
messageSig.substring(2) // Signature | ||
) // receipt + signature = attestation in EIP712 format |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this comment is wrong. EIP712
is not a "format" for interchange. It's a method for hashing and signing - not serialization. There is an encodeStruct
method, but this encoding does not match what these methods do to read/write structs from bytes. encodeStruct
is not lossless, and therefore cannot in principle be used for serialization even if you wanted to. So, the string returned by this method cannot be of an attestation in "EIP712 format".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand, I think I did a bad use of the language. I'd been using EIP712 "format" when it really is a standard, and I'd been calling the format the end result of encode/hashing the struct with the type_hash and each content field processed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure that we're on the same page. This looks like some kind of ABI encoded SignedAttestation
, whereas the comment says it's EIP-712.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my comment I was referring to when I used EIP712 in the code.
About that particular section, the comment is not ok, it is just the attestation what that function returns, the receipt + signature appended.
test/lib/attestation.js
Outdated
|
||
// Message | ||
const message = createMessage(domainSeparatorHash, attestationHash) | ||
// Attestation signing wrapped in EIP721 format |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has a typo and it says EIP721 when it meant EIP712, but to reiterate, there is no EIP-712 format.
@That3Percent I pushed changes including:
|
- remove BytesLib dependency and use Solidity 0.6 array slice - remove ECDSA-openzeppelin dependency for ecrecover() - refactor Attestation struct to follow RFC - change language around attestation and receipts - ignore linting of the DisputeManager till support for array slices is added
…ature sent by clients
f0d8fcd
to
0956b1b
Compare
Related to https://github.com/graphprotocol/network-rfcs/pull/24