Skip to content

Commit

Permalink
added signature from server side for signedAdd
Browse files Browse the repository at this point in the history
  • Loading branch information
tienngovan committed Feb 26, 2024
1 parent 735a1a0 commit 93c5259
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 57 deletions.
57 changes: 40 additions & 17 deletions contracts/OwnerData.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import "@openzeppelin/contracts/utils/Context.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/Strings.sol";

contract FFV5 {
function balanceOf(address account, uint256 id) public view returns (uint256) {}
contract IFF {
function ownerOf(uint256 tokenId) public view returns (address) {}
}

string constant SIGNED_MESSAGE = "Authorize to write your data to the contract";
Expand All @@ -17,24 +17,39 @@ contract OwnerData is Context {
bytes dataHash;
string metadata;
}
struct Signature {
bytes ownerSign;
uint256 expiryBlock;
bytes32 r;
bytes32 s;
uint8 v;
}

address private _trustee;

// contractAddress => tokenID => Data[]
mapping(address => mapping(uint256 => Data[])) private _tokenData;
// contractAddress => tokenID => owner => bool
mapping(address => mapping(uint256 => mapping(address => bool))) private _tokenDataOwner;

constructor(address trustee_) {
require(trustee_ != address(0), "OwnerData: Trustee is the zero address");
_trustee = trustee_;
}

function add(address contractAddress, uint256 tokenID, Data calldata data) external {
_addData(_msgSender(), contractAddress, tokenID, data);
}

function signedAdd(
address contractAddress,
uint256 tokenID,
bytes memory signature,
Data calldata data
Data calldata data,
Signature calldata signature
) external {
address signer = _verifySignature(signature);
_addData(signer, contractAddress, tokenID, data);
_validateSignature(signature);
address account = _recoverOwnerSignature(signature.ownerSign);
_addData(account, contractAddress, tokenID, data);
}


Expand All @@ -48,12 +63,8 @@ contract OwnerData is Context {
uint256 tokenID,
Data calldata data
) private {
if (!_isOwner(contractAddress, tokenID, sender)) {
revert NotOwner(sender, contractAddress, tokenID);
}
if (data.owner != sender) {
revert OwnerMismatch(data.owner, sender);
}
require(_isOwner(contractAddress, tokenID, sender), "OwnerData: sender is not the owner");
require(data.owner == sender, "OwnerData: data owner mismatch");
require(data.dataHash.length > 0, "OwnerData: dataHash is empty");
require(!_tokenDataOwner[contractAddress][tokenID][data.owner], "OwnerData: data already added");

Expand All @@ -63,17 +74,29 @@ contract OwnerData is Context {
emit DataAdded(contractAddress, tokenID, data);
}

function _verifySignature(bytes memory signature) private view returns (address) {
function _validateSignature(Signature calldata signature) private view {
require(signature.expiryBlock >= block.number, "OwnerData: signature expired");

bytes32 message = keccak256(
abi.encode(block.chainid, address(this), signature.ownerSign, signature.expiryBlock)
);
address reqSigner = ECDSA.recover(
ECDSA.toEthSignedMessageHash(message),
signature.v,
signature.r,
signature.s
);
require(reqSigner == _trustee, "OwnerData: invalid signature");
}

function _recoverOwnerSignature(bytes memory signature) private view returns (address) {
bytes memory message = abi.encodePacked(SIGNED_MESSAGE, " ", Strings.toHexString(address(this)), ".");
return ECDSA.recover(ECDSA.toEthSignedMessageHash(message), signature);
}

function _isOwner(address contractAddress, uint256 tokenID, address account) private view returns (bool) {
return FFV5(contractAddress).balanceOf(account, tokenID) > 0;
return IFF(contractAddress).ownerOf(tokenID) == account;
}

event DataAdded(address indexed contractAddress, uint256 indexed tokenID, Data data);

error NotOwner(address caller, address contractAddress, uint256 tokenID);
error OwnerMismatch(address dataOwner, address caller);
}
7 changes: 6 additions & 1 deletion migrations/252_owner_data.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
var OwnerData = artifacts.require("OwnerData");
const argv = require("minimist")(process.argv.slice(2), {
string: ["trustee"],
});

module.exports = function (deployer) {
deployer.deploy(OwnerData);
const trustee =
argv.trustee || "0xdB33365a8730de2F7574ff1189fB9D337bF4c36d";
deployer.deploy(OwnerData, trustee);
};
93 changes: 54 additions & 39 deletions test/owner_data.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
const OwnerData = artifacts.require("OwnerData");
const FeralfileExhibitionV5 = artifacts.require("FeralfileExhibitionV5");
const FeralfileExhibitionV4 = artifacts.require("FeralfileExhibitionV4");
const FeralfileVault = artifacts.require("FeralfileVault");

const CONTRACT_URI = "ipfs://QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc";
const TOKEN_BASE_URI = "ipfs://QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc";
const COST_RECEIVER = "0x46f2B641d8702f29c45f6D06292dC34Eb9dB1801";

const { bufferToHex } = require("ethereumjs-util");
Expand All @@ -16,31 +15,33 @@ contract("OwnerData", async (accounts) => {
before(async function () {
this.signer = accounts[0];
this.trustee = accounts[1];
this.signTrustee = accounts[5];
this.vault = await FeralfileVault.new(this.signer);
this.ownerDataContract = await OwnerData.new();
this.ownerDataContract = await OwnerData.new(this.signTrustee);
this.seriesIds = [1, 2];
this.seriesMaxSupply = [100, 1];
this.seriesArtworkMaxSupply = [1, 100];
this.exhibitionContract = await FeralfileExhibitionV5.new(
TOKEN_BASE_URI,
CONTRACT_URI,
this.exhibitionContract = await FeralfileExhibitionV4.new(
"Feral File V4 Test",
"FFv4",
true,
true,
this.signer,
this.vault.address,
COST_RECEIVER,
true,
CONTRACT_URI,
this.seriesIds,
this.seriesMaxSupply,
this.seriesArtworkMaxSupply
this.seriesMaxSupply
);

await this.exhibitionContract.mintArtworks([
[1, 1, accounts[0], 1],
[1, 2, accounts[1], 1],
[1, 3, accounts[2], 1],
[1, 4, accounts[0], 1],
[1, 5, accounts[1], 1],
[1, 6, accounts[2], 1],
[1, 7, "0x23221e5403511CeC833294D2B1B006e9D639A61b", 1],
[1, 1, accounts[0]],
[1, 2, accounts[1]],
[1, 3, accounts[2]],
[1, 4, accounts[0]],
[1, 5, accounts[1]],
[1, 6, accounts[2]],
[1, 7, "0x23221e5403511CeC833294D2B1B006e9D639A61b"],
]);
await this.exhibitionContract.addTrustee(this.trustee);
});
Expand Down Expand Up @@ -114,19 +115,14 @@ contract("OwnerData", async (accounts) => {
assert.equal(bytesToString(tx1.logs[0].args.data.dataHash), cid1);

// Transfer to account 3
await this.exhibitionContract.safeTransferFrom(
await this.exhibitionContract.transferFrom(
accounts[1],
accounts[2],
2,
1,
web3.utils.fromAscii(""),
{ from: accounts[1] }
);
const acc2Balance = await this.exhibitionContract.balanceOf(
accounts[2],
2
);
assert.equal(acc2Balance, 1);
const acc2Owner = await this.exhibitionContract.ownerOf(2);
assert.equal(acc2Owner, accounts[2]);

const tx2 = await this.ownerDataContract.add(
this.exhibitionContract.address,
Expand All @@ -137,19 +133,14 @@ contract("OwnerData", async (accounts) => {
assert.equal(bytesToString(tx2.logs[0].args.data.dataHash), cid2);

// Transfer to account 4
await this.exhibitionContract.safeTransferFrom(
await this.exhibitionContract.transferFrom(
accounts[2],
accounts[4],
2,
1,
web3.utils.fromAscii(""),
{ from: accounts[2] }
);
const acc4Balance = await this.exhibitionContract.balanceOf(
accounts[4],
2
);
assert.equal(acc4Balance, 1);
const acc4Owner = await this.exhibitionContract.ownerOf(2);
assert.equal(acc4Owner, accounts[4]);

const tx3 = await this.ownerDataContract.add(
this.exhibitionContract.address,
Expand Down Expand Up @@ -185,11 +176,7 @@ contract("OwnerData", async (accounts) => {
[accounts[1], cidBytes, "{duration: 1000}"]
);
} catch (error) {
assert.ok(
error.message.includes(
"VM Exception while processing transaction: revert"
)
);
assert.equal(error.reason, "OwnerData: data owner mismatch");
}
});

Expand Down Expand Up @@ -226,11 +213,39 @@ contract("OwnerData", async (accounts) => {
"0x5cd8bcda59dd3a9988bd20bdbdea7225a4a57949d12b9a527caf3ff819941d7f";
const { signature } = await web3.eth.accounts.sign(msgHash, privateKey);

const expiryTime = (new Date().getTime() / 1000 + 300).toFixed(0);
const chainId = await web3.eth.getChainId();
const signedParams = web3.eth.abi.encodeParameters(
["uint", "address", "bytes", "uint256"],
[
BigInt(chainId).toString(),
this.ownerDataContract.address,
signature,
expiryTime,
]
);

const hash = web3.utils.keccak256(signedParams);
const trusteeSignature = await web3.eth.sign(hash, accounts[5]);
const sig = trusteeSignature.substr(2);
const r = "0x" + sig.slice(0, 64);
const s = "0x" + sig.slice(64, 128);
const v = "0x" + sig.slice(128, 130);

// sign params
const signs = [
signature,
expiryTime,
r,
s,
web3.utils.toDecimal(v) + 27,
];

const tx = await this.ownerDataContract.signedAdd(
this.exhibitionContract.address,
7,
signature,
data
data,
signs
);
assert.equal(tx.logs[0].event, "DataAdded");
});
Expand Down

0 comments on commit 93c5259

Please sign in to comment.