-
Notifications
You must be signed in to change notification settings - Fork 1
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
feat: signatures #96
base: master
Are you sure you want to change the base?
feat: signatures #96
Changes from all commits
d74a730
4e0e34b
0376b07
5d975a5
71b03bc
8c361a9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import "./Signature.sol"; | ||
|
||
contract PostageStampSig is Signature { | ||
/** Hash of the message to sign */ | ||
function getMessageHash( | ||
bytes32 _chunkAddr, | ||
bytes32 _batchId, | ||
uint64 _index, | ||
uint64 _timeStamp | ||
) public pure returns (bytes32) { | ||
return keccak256(abi.encodePacked(_chunkAddr, _batchId, _index, _timeStamp)); | ||
} | ||
|
||
function verify( | ||
address _signer, // signer Ethereum address to check against | ||
bytes memory _signature, | ||
bytes32 _chunkAddr, | ||
bytes32 _postageId, | ||
uint64 _index, | ||
uint64 _timeStamp | ||
) public pure returns (bool) { | ||
bytes32 messageHash = getMessageHash(_chunkAddr, _postageId, _index, _timeStamp); | ||
bytes32 ethMessageHash = Signature.getEthSignedMessageHash(messageHash); | ||
|
||
return Signature.recoverSigner(ethMessageHash, _signature) == _signer; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
// based on: https://solidity-by-example.org/signature/ | ||
|
||
/* Signature Verification | ||
|
||
How to Sign and Verify | ||
# Signing | ||
1. Create message to sign | ||
2. Hash the message | ||
3. Sign the hash (off chain, keep your private key secret) | ||
|
||
# Verify | ||
1. Recreate hash from the original message | ||
2. Recover signer from signature and hash | ||
3. Compare recovered signer to claimed signer | ||
*/ | ||
|
||
contract Signature { | ||
/** Appends Ethereum Signed Message prefix to the message hash */ | ||
function getEthSignedMessageHash( | ||
bytes32 _messageHash | ||
) public pure returns (bytes32) { | ||
/* | ||
Signature is produced by signing a keccak256 hash with the following format: | ||
"\x19Ethereum Signed Message\n" + len(msg) + msg | ||
*/ | ||
return | ||
keccak256( | ||
abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash) | ||
); | ||
} | ||
|
||
function recoverSigner( | ||
bytes32 _ethSignedMessageHash, // it has to be prefixed message: https://ethereum.stackexchange.com/questions/19582/does-ecrecover-in-solidity-expects-the-x19ethereum-signed-message-n-prefix/21037 | ||
bytes memory _signature | ||
) public pure returns (address) { | ||
(bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature); | ||
|
||
return ecrecover(_ethSignedMessageHash, v, r, s); | ||
} | ||
|
||
function splitSignature( | ||
bytes memory sig | ||
) public pure returns (bytes32 r_, bytes32 s_, uint8 v_) { | ||
require(sig.length == 65, "invalid signature length"); | ||
|
||
assembly { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if you can avoid assembly and send r,s,v directly into recoverSigner, you would simplyfy contract There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. but the assemly part sets values for |
||
/* | ||
verbose explanation: https://ethereum.stackexchange.com/questions/135591/split-signature-function-in-solidity-by-example-docs | ||
First 32 bytes stores the length of the signature | ||
|
||
add(sig, 32) = pointer of sig + 32 | ||
effectively, skips first 32 bytes of signature | ||
|
||
mload(p) loads next 32 bytes starting at the memory address p into memory | ||
*/ | ||
|
||
// first 32 bytes, after the length prefix | ||
r_ := mload(add(sig, 32)) | ||
// second 32 bytes | ||
s_ := mload(add(sig, 64)) | ||
// final byte (first byte of the next 32 bytes) | ||
v_ := byte(0, mload(add(sig, 96))) | ||
} | ||
|
||
// implicitly return (r, s, v) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import "./Signature.sol"; | ||
|
||
contract SocSig is Signature { | ||
/** Hash of the message to sign */ | ||
function getMessageHash( | ||
bytes32 _identifier, | ||
bytes32 _chunkAddr | ||
) public pure returns (bytes32) { | ||
return keccak256(abi.encodePacked(_identifier, _chunkAddr)); | ||
} | ||
|
||
function verify( | ||
address _signer, // signer Ethereum address to check against | ||
bytes memory _signature, | ||
bytes32 _identifier, | ||
bytes32 _chunkAddr | ||
) public pure returns (bool) { | ||
bytes32 messageHash = getMessageHash(_identifier, _chunkAddr); | ||
bytes32 ethMessageHash = Signature.getEthSignedMessageHash(messageHash); | ||
|
||
return Signature.recoverSigner(ethMessageHash, _signature) == _signer; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { expect } from 'chai' | ||
import { ethers } from 'hardhat' | ||
|
||
describe('Signatues', function () { | ||
it('should recover postage stamp signature', async function () { | ||
const accounts = await ethers.getSigners() | ||
|
||
const PostageStampSig = await ethers.getContractFactory('PostageStampSig') | ||
const postageStampSig = await PostageStampSig.deploy() | ||
await postageStampSig.deployed() | ||
|
||
const signer = accounts[0] | ||
const chunkAddrHex = '0x98371fb1297da62c1355abd8f9a7e43dd20cd5ddb9db7ba7c2d99e35e7afb58f' | ||
const batchIdHex = '0xba73d3e3dfe9bd95595c6fa67f682adf3fde207ab339cffd7cc24a1905389a9c' | ||
const index = BigInt('1039382085632') | ||
const ts = BigInt('1673624779490180673') | ||
|
||
const hash = await postageStampSig.getMessageHash(chunkAddrHex, batchIdHex, index, ts) | ||
const sig = await signer.signMessage(ethers.utils.arrayify(hash)) | ||
|
||
const ethHash = await postageStampSig.getEthSignedMessageHash(hash) | ||
|
||
expect(signer.address).to.equal(await postageStampSig.recoverSigner(ethHash, sig)) | ||
|
||
// Correct signature and message returns true | ||
expect(await postageStampSig.verify(signer.address, sig, chunkAddrHex, batchIdHex, index, ts)).to.equal(true) | ||
|
||
// Incorrect message returns false | ||
expect(await postageStampSig.verify(signer.address, sig, chunkAddrHex, batchIdHex, '0', ts)).to.equal(false) | ||
}) | ||
|
||
it('should recover Single Owner Chunk signature', async function () { | ||
const accounts = await ethers.getSigners() | ||
|
||
const SocSig = await ethers.getContractFactory('SocSig') | ||
const socSig = await SocSig.deploy() | ||
await socSig.deployed() | ||
|
||
const signer = accounts[0] | ||
const topicHex = '0x0000000000000000000000000000000000000000000000000000000000000000' | ||
const chunkAddrHex = '0x98371fb1297da62c1355abd8f9a7e43dd20cd5ddb9db7ba7c2d99e35e7afb58f' | ||
|
||
const hash = await socSig.getMessageHash(topicHex, chunkAddrHex) | ||
const sig = await signer.signMessage(ethers.utils.arrayify(hash)) | ||
|
||
const ethHash = await socSig.getEthSignedMessageHash(hash) | ||
|
||
expect(signer.address).to.equal(await socSig.recoverSigner(ethHash, sig)) | ||
|
||
// Correct signature and message returns true | ||
expect(await socSig.verify(signer.address, sig, topicHex, chunkAddrHex)).to.equal(true) | ||
|
||
// Incorrect message returns false | ||
expect( | ||
await socSig.verify( | ||
signer.address, | ||
sig, | ||
'0x0000000000000000000000000000000000000000000000000000000000000001', | ||
chunkAddrHex, | ||
), | ||
).to.equal(false) | ||
}) | ||
}) |
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 added prefix string would make it an invalid as transaction
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 is the way how Bee client signs all message
https://github.com/ethersphere/bee/blob/master/pkg/crypto/signer.go#L82-L83
we do not check signed blockchain transactions with this function.