Skip to content

Commit

Permalink
Version 2.2.0
Browse files Browse the repository at this point in the history
* ✨(Identity) Add isClaimValid for self-attested claims

* 🔖(2.2.0) Update version and CHANGELOG

* 🔖(2.2.0) Update version and CHANGELOG

* ✨(factory) Add getter for authority
  • Loading branch information
Nakasar authored Oct 17, 2023
1 parent 3f3b07a commit c42610a
Show file tree
Hide file tree
Showing 13 changed files with 119 additions and 50 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.2.0]

### Added
- Identities are now required to implement the standardized `function isClaimValid(IIdentity _identity, uint256
claimTopic, bytes calldata sig, bytes calldata data) external view returns (bool)`, used for self-attested claims
(`_identity` is the address of the Identity contract).
- Implemented the `isClaimValid` function in the `Identity` contract.
- IdFactory now implements the `implementationAuthority()` getter.

## [2.1.0]

### Added
Expand Down
36 changes: 1 addition & 35 deletions contracts/ClaimIssuer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ contract ClaimIssuer is IClaimIssuer, Identity {
uint256 claimTopic,
bytes memory sig,
bytes memory data)
external override view returns (bool claimValid)
public override(Identity, IClaimIssuer) view returns (bool claimValid)
{
bytes32 dataHash = keccak256(abi.encode(_identity, claimTopic, data));
// Use abi.encodePacked to concatenate the message prefix and the message to sign.
Expand Down Expand Up @@ -79,38 +79,4 @@ contract ClaimIssuer is IClaimIssuer, Identity {

return false;
}

/**
* @dev See {IClaimIssuer-getRecoveredAddress}.
*/
function getRecoveredAddress(bytes memory sig, bytes32 dataHash)
public override
pure
returns (address addr)
{
bytes32 ra;
bytes32 sa;
uint8 va;

// Check the signature length
if (sig.length != 65) {
return address(0);
}

// Divide the signature in r, s and v variables
// solhint-disable-next-line no-inline-assembly
assembly {
ra := mload(add(sig, 32))
sa := mload(add(sig, 64))
va := byte(0, mload(add(sig, 96)))
}

if (va < 27) {
va += 27;
}

address recoveredAddress = ecrecover(dataHash, va, ra, sa);

return (recoveredAddress);
}
}
73 changes: 73 additions & 0 deletions contracts/Identity.sol
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,79 @@ contract Identity is Storage, IIdentity, Version {
return false;
}

/**
* @dev Checks if a claim is valid. Claims issued by the identity are self-attested claims. They do not have a
* built-in revocation mechanism and are considered valid as long as their signature is valid and they are still
* stored by the identity contract.
* @param _identity the identity contract related to the claim
* @param claimTopic the claim topic of the claim
* @param sig the signature of the claim
* @param data the data field of the claim
* @return claimValid true if the claim is valid, false otherwise
*/
function isClaimValid(
IIdentity _identity,
uint256 claimTopic,
bytes memory sig,
bytes memory data)
public override virtual view returns (bool claimValid)
{
bytes32 dataHash = keccak256(abi.encode(_identity, claimTopic, data));
// Use abi.encodePacked to concatenate the message prefix and the message to sign.
bytes32 prefixedHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", dataHash));

// Recover address of data signer
address recovered = getRecoveredAddress(sig, prefixedHash);

// Take hash of recovered address
bytes32 hashedAddr = keccak256(abi.encode(recovered));

// Does the trusted identifier have they key which signed the user's claim?
// && (isClaimRevoked(_claimId) == false)
if (keyHasPurpose(hashedAddr, 3)) {
return true;
}

return false;
}

/**
* @dev returns the address that signed the given data
* @param sig the signature of the data
* @param dataHash the data that was signed
* returns the address that signed dataHash and created the signature sig
*/
function getRecoveredAddress(bytes memory sig, bytes32 dataHash)
public
pure
returns (address addr)
{
bytes32 ra;
bytes32 sa;
uint8 va;

// Check the signature length
if (sig.length != 65) {
return address(0);
}

// Divide the signature in r, s and v variables
// solhint-disable-next-line no-inline-assembly
assembly {
ra := mload(add(sig, 32))
sa := mload(add(sig, 64))
va := byte(0, mload(add(sig, 96)))
}

if (va < 27) {
va += 27;
}

address recoveredAddress = ecrecover(dataHash, va, ra, sa);

return (recoveredAddress);
}

/**
* @notice Initializer internal function for the Identity contract.
*
Expand Down
5 changes: 5 additions & 0 deletions contracts/factory/IIdFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,9 @@ interface IIdFactory {
* @param _salt the salt used for deployment
*/
function isSaltTaken(string calldata _salt) external view returns (bool);

/**
* @dev getter for the implementation authority used by this factory.
*/
function implementationAuthority() external view returns (address);
}
7 changes: 7 additions & 0 deletions contracts/factory/IdFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,13 @@ contract IdFactory is IIdFactory, Ownable {
return _tokenFactories[_factory];
}

/**
* @dev See {IdFactory-implementationAuthority}.
*/
function implementationAuthority() public override view returns (address) {
return _implementationAuthority;
}

// deploy function with create2 opcode call
// returns the address of the contract created
function _deploy(string memory salt, bytes memory bytecode) private returns (address) {
Expand Down
8 changes: 0 additions & 8 deletions contracts/interface/IClaimIssuer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,4 @@ interface IClaimIssuer is IIdentity {
bytes calldata sig,
bytes calldata data)
external view returns (bool);

/**
* @dev returns the address that signed the given data
* @param sig the signature of the data
* @param dataHash the data that was signed
* returns the address that signed dataHash and created the signature sig
*/
function getRecoveredAddress(bytes calldata sig, bytes32 dataHash) external pure returns (address);
}
17 changes: 16 additions & 1 deletion contracts/interface/IIdentity.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,19 @@ import "./IERC734.sol";
import "./IERC735.sol";

// solhint-disable-next-line no-empty-blocks
interface IIdentity is IERC734, IERC735 {}
interface IIdentity is IERC734, IERC735 {
/**
* @dev Checks if a claim is valid.
* @param _identity the identity contract related to the claim
* @param claimTopic the claim topic of the claim
* @param sig the signature of the claim
* @param data the data field of the claim
* @return claimValid true if the claim is valid, false otherwise
*/
function isClaimValid(
IIdentity _identity,
uint256 claimTopic,
bytes calldata sig,
bytes calldata data)
external view returns (bool);
}
4 changes: 2 additions & 2 deletions contracts/version/Version.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ contract Version {
* @dev Returns the string of the current version.
*/
function version() external pure returns (string memory) {
// version 2.0.1
return "2.0.1";
// version 2.2.0
return "2.2.0";
}
}
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onchain-id/solidity",
"version": "2.1.0",
"version": "2.2.0",
"description": "EVM solidity smart contracts for Blockchain OnchainID identities.",
"files": [
"artifacts",
Expand Down
2 changes: 1 addition & 1 deletion test/gateway/gateway.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -824,7 +824,7 @@ describe('Gateway', () => {

const tx = await gateway.connect(deployerWallet).callFactory(new ethers.utils.Interface(['function addTokenFactory(address)']).encodeFunctionData('addTokenFactory', [bobWallet.address]));

expect(tx).to.emit(gateway, "TokenFactoryAdded").withArgs(bobWallet.address);
expect(tx).to.emit(identityFactory, "TokenFactoryAdded").withArgs(bobWallet.address);
});
});
});
Expand Down
2 changes: 2 additions & 0 deletions test/identities/claims.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ describe('Identity', () => {

const tx = await aliceIdentity.connect(aliceWallet).addClaim(claim.topic, claim.scheme, claim.issuer, claim.signature, claim.data, claim.uri);
await expect(tx).to.emit(aliceIdentity, 'ClaimAdded').withArgs(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(['address', 'uint256'], [claim.issuer, claim.topic])), claim.topic, claim.scheme, claim.issuer, claim.signature, claim.data, claim.uri);
await expect(aliceIdentity.isClaimValid(claim.identity, claim.topic, claim.signature, claim.data)).to.eventually.equal(false);
});
});

Expand Down Expand Up @@ -64,6 +65,7 @@ describe('Identity', () => {
await expect(tx).to.emit(aliceIdentity, 'ClaimAdded').withArgs(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(['address', 'uint256'], [claim.issuer, claim.topic])), claim.topic, claim.scheme, claim.issuer, claim.signature, claim.data, claim.uri);
await expect(tx).to.emit(aliceIdentity, 'Approved');
await expect(tx).to.emit(aliceIdentity, 'Executed');
await expect(aliceIdentity.isClaimValid(claim.identity, claim.topic, claim.signature, claim.data)).to.eventually.equal(true);
});
});

Expand Down
2 changes: 1 addition & 1 deletion test/identities/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@ describe('Identity', () => {
it('should return the version of the implementation', async () => {
const {identityImplementation} = await loadFixture(deployIdentityFixture);

expect(await identityImplementation.version()).to.equal('2.0.1');
expect(await identityImplementation.version()).to.equal('2.2.0');
});
});

0 comments on commit c42610a

Please sign in to comment.