Skip to content

Commit 5af30a1

Browse files
authored
Add an ability to transit root with signature in Replicator (#36)
* Added an ability to transit root with signature in RegistrationSMTReplicator * Prepared for publishing * Resolved review comments
1 parent 9344f42 commit 5af30a1

File tree

8 files changed

+127
-28
lines changed

8 files changed

+127
-28
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## [0.3.1]
4+
5+
* Enhanced `RegistrationSMTReplicator` with signature-based root transition.
6+
37
## [0.3.0]
48

59
* Added `RegistrationSMTReplicator` contract and `AQueryProofExecutor` abstract contract to accommodate integration with Rarime.

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ Afterwards, you will be able to use the application by calling the `Registration
5252
> [!NOTE]
5353
> This is experimental, state of the art software. Behold and use at your own risk.
5454
55+
Here you will find a guide for the on-chain integration with the SDK, which is provided as a part of the NPM package: [Guide: Setting up on-chain user verification with ZK Passport](https://docs.rarimo.com/zk-passport/guide-on-chain-verification/)
56+
5557
## License
5658

5759
The smart contracts are released under the MIT License.

contracts/mock/sdk/ProofBuilderTest.sol

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ contract ProofBuilderTest is AQueryProofExecutor {
1717
}
1818

1919
function _buildPublicSignals(
20-
bytes32 registrationRoot_,
21-
uint256 currentDate_,
22-
bytes memory userPayload_
23-
) public override returns (uint256) {
20+
bytes32,
21+
uint256,
22+
bytes memory
23+
) public pure override returns (uint256) {
2424
return 0;
2525
}
2626

@@ -48,7 +48,7 @@ contract ProofBuilderTest is AQueryProofExecutor {
4848
}
4949
}
5050

51-
function testEquivalencePart1_NewBuilder() external view {
51+
function testEquivalencePart1_NewBuilder() external pure {
5252
uint256 nullifier = 1;
5353
uint256 selector = 2;
5454

@@ -61,7 +61,7 @@ contract ProofBuilderTest is AQueryProofExecutor {
6161
_compareArrays(originalPubSignals, libPubSignals);
6262
}
6363

64-
function testEquivalencePart2_Name() external view {
64+
function testEquivalencePart2_Name() external pure {
6565
uint256 nullifier = 1;
6666
uint256 selector = 2;
6767
uint256 name_ = 3;
@@ -82,7 +82,7 @@ contract ProofBuilderTest is AQueryProofExecutor {
8282
_compareArrays(originalPubSignals, libPubSignals);
8383
}
8484

85-
function testEquivalencePart3_NationalityCitizenshipSex() external view {
85+
function testEquivalencePart3_NationalityCitizenshipSex() external pure {
8686
uint256 nullifier = 1;
8787
uint256 selector = 2;
8888
uint256 nationality_ = 5;
@@ -106,7 +106,7 @@ contract ProofBuilderTest is AQueryProofExecutor {
106106
_compareArrays(originalPubSignals, libPubSignals);
107107
}
108108

109-
function testEquivalencePart4_Event() external view {
109+
function testEquivalencePart4_Event() external pure {
110110
uint256 nullifier = 1;
111111
uint256 selector = 2;
112112
uint256 eventId_ = 9;
@@ -160,7 +160,7 @@ contract ProofBuilderTest is AQueryProofExecutor {
160160
_compareArrays(originalPubSignals, libPubSignals);
161161
}
162162

163-
function testEquivalencePart7_TimestampBounds() external view {
163+
function testEquivalencePart7_TimestampBounds() external pure {
164164
uint256 nullifier = 1;
165165
uint256 selector = 2;
166166
uint256 timestampLowerbound_ = 14;
@@ -184,7 +184,7 @@ contract ProofBuilderTest is AQueryProofExecutor {
184184
_compareArrays(originalPubSignals, libPubSignals);
185185
}
186186

187-
function testEquivalencePart8_IdentityCounterBounds() external view {
187+
function testEquivalencePart8_IdentityCounterBounds() external pure {
188188
uint256 nullifier = 1;
189189
uint256 selector = 2;
190190
uint256 identityCounterLowerbound_ = 16;
@@ -208,7 +208,7 @@ contract ProofBuilderTest is AQueryProofExecutor {
208208
_compareArrays(originalPubSignals, libPubSignals);
209209
}
210210

211-
function testEquivalencePart9_BirthDateBounds() external view {
211+
function testEquivalencePart9_BirthDateBounds() external pure {
212212
uint256 nullifier = 1;
213213
uint256 selector = 2;
214214
uint256 birthDateLowerbound_ = 18;
@@ -232,7 +232,7 @@ contract ProofBuilderTest is AQueryProofExecutor {
232232
_compareArrays(originalPubSignals, libPubSignals);
233233
}
234234

235-
function testEquivalencePart10_ExpirationDateBounds() external view {
235+
function testEquivalencePart10_ExpirationDateBounds() external pure {
236236
uint256 nullifier = 1;
237237
uint256 selector = 2;
238238
uint256 expirationDateLowerbound_ = 20;
@@ -256,7 +256,7 @@ contract ProofBuilderTest is AQueryProofExecutor {
256256
_compareArrays(originalPubSignals, libPubSignals);
257257
}
258258

259-
function testEquivalencePart11_CitizenshipMask() external view {
259+
function testEquivalencePart11_CitizenshipMask() external pure {
260260
uint256 nullifier = 1;
261261
uint256 selector = 2;
262262
uint256 citizenshipMask_ = 22;

contracts/mock/verifiers/VerifierMock.sol

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ pragma solidity ^0.8.21;
33

44
contract VerifierMock {
55
function verifyProof(
6-
uint256[2] calldata pA_,
7-
uint256[2][2] calldata pB_,
8-
uint256[2] calldata pC_,
9-
uint256[4] calldata pubSignals_
10-
) public view returns (bool) {
6+
uint256[2] calldata,
7+
uint256[2][2] calldata,
8+
uint256[2] calldata,
9+
uint256[4] calldata
10+
) public pure returns (bool) {
1111
return true;
1212
}
1313
}

contracts/sdk/RegistrationSMTReplicator.sol

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.22;
33

4+
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
45
import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
56
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
7+
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
68
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
79

810
import {SetHelper} from "@solarity/solidity-lib/libs/arrays/SetHelper.sol";
@@ -23,6 +25,9 @@ contract RegistrationSMTReplicator is IPoseidonSMT, AMultiOwnable, UUPSUpgradeab
2325
using EnumerableSet for EnumerableSet.AddressSet;
2426

2527
uint256 public constant ROOT_VALIDITY = 1 hours;
28+
string public constant REGISTRATION_ROOT_PREFIX = "Rarimo root";
29+
30+
address public sourceSMT;
2631

2732
bytes32 public latestRoot;
2833
uint256 public latestTimestamp;
@@ -40,9 +45,14 @@ contract RegistrationSMTReplicator is IPoseidonSMT, AMultiOwnable, UUPSUpgradeab
4045

4146
error NotAnOracle(address sender);
4247

43-
function __RegistrationSMTReplicator_init(address[] memory oracles_) external initializer {
48+
function __RegistrationSMTReplicator_init(
49+
address[] memory oracles_,
50+
address sourceSMT_
51+
) external initializer {
4452
__AMultiOwnable_init();
4553

54+
sourceSMT = sourceSMT_;
55+
4656
_oracles.add(oracles_);
4757
}
4858

@@ -60,6 +70,13 @@ contract RegistrationSMTReplicator is IPoseidonSMT, AMultiOwnable, UUPSUpgradeab
6070
_oracles.remove(oracles_);
6171
}
6272

73+
/*
74+
* @notice Updates the source SMT address.
75+
*/
76+
function setSourceSMT(address newSourceSMT_) external onlyOwner {
77+
sourceSMT = newSourceSMT_;
78+
}
79+
6380
/*
6481
* @notice Transitions the root of the Registration SMT.
6582
* @param newRoot_ The new root to be set.
@@ -76,6 +93,40 @@ contract RegistrationSMTReplicator is IPoseidonSMT, AMultiOwnable, UUPSUpgradeab
7693
_updateRoot(newRoot_, transitionTimestamp_);
7794
}
7895

96+
/*
97+
* @notice Transitions the root of the Registration SMT with signature verification.
98+
* @param newRoot_ The new root to be set.
99+
* @param transitionTimestamp_ The timestamp of the transition.
100+
* @param signature_ The signature from the sourceSMT verifying the root transition.
101+
*/
102+
function transitionRootWithSignature(
103+
bytes32 newRoot_,
104+
uint256 transitionTimestamp_,
105+
bytes memory signature_
106+
) external virtual {
107+
if (_roots[newRoot_] != 0) {
108+
return;
109+
}
110+
111+
bytes32 messageHash_ = keccak256(
112+
abi.encodePacked(
113+
REGISTRATION_ROOT_PREFIX,
114+
sourceSMT,
115+
address(this),
116+
newRoot_,
117+
transitionTimestamp_
118+
)
119+
);
120+
121+
address signer_ = ECDSA.recover(
122+
MessageHashUtils.toEthSignedMessageHash(messageHash_),
123+
signature_
124+
);
125+
require(isOracle(signer_), NotAnOracle(signer_));
126+
127+
_updateRoot(newRoot_, transitionTimestamp_);
128+
}
129+
79130
/*
80131
* @notice Checks if the root is valid.
81132
* The root is valid if it is the latest transited root or if it was transitioned within the ROOT_VALIDITY period.

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@rarimo/passport-contracts",
3-
"version": "0.3.0",
3+
"version": "0.3.1",
44
"license": "MIT",
55
"author": "Zero Block Global Foundation",
66
"readme": "README.md",

test/sdk/replication/RegistrationSMTReplicator.test.ts

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,20 @@ describe("RegistrationSMTReplicator", () => {
1515
let ORACLE1: SignerWithAddress;
1616
let ORACLE2: SignerWithAddress;
1717
let OTHER: SignerWithAddress;
18+
let SOURCE_SMT: SignerWithAddress;
1819

1920
let replicator: RegistrationSMTReplicator;
21+
const REGISTRATION_ROOT_PREFIX = "Rarimo root";
2022

2123
before("setup", async () => {
22-
[OWNER, ORACLE1, ORACLE2, OTHER] = await ethers.getSigners();
24+
[OWNER, ORACLE1, ORACLE2, OTHER, SOURCE_SMT] = await ethers.getSigners();
2325

2426
const implementation = await ethers.deployContract("RegistrationSMTReplicator");
2527

2628
const proxy = await ethers.deployContract("ERC1967Proxy", [await implementation.getAddress(), "0x"]);
2729
replicator = await ethers.getContractAt("RegistrationSMTReplicator", await proxy.getAddress());
2830

29-
await replicator.__RegistrationSMTReplicator_init([ORACLE1]);
31+
await replicator.__RegistrationSMTReplicator_init([ORACLE1.address], SOURCE_SMT.address);
3032

3133
await reverter.snapshot();
3234
});
@@ -39,13 +41,13 @@ describe("RegistrationSMTReplicator", () => {
3941

4042
expect(await replicator.isOracle(ORACLE1.address)).to.be.true;
4143
expect(await replicator.getOracles()).to.deep.equal([ORACLE1.address]);
44+
expect(await replicator.sourceSMT()).to.equal(SOURCE_SMT.address);
4245
});
4346

4447
it("should revert if trying to initialize twice", async () => {
45-
await expect(replicator.__RegistrationSMTReplicator_init([OTHER.address])).to.be.revertedWithCustomError(
46-
replicator,
47-
"InvalidInitialization",
48-
);
48+
await expect(
49+
replicator.__RegistrationSMTReplicator_init([OTHER.address], OTHER.address),
50+
).to.be.revertedWithCustomError(replicator, "InvalidInitialization");
4951
});
5052
});
5153

@@ -149,6 +151,46 @@ describe("RegistrationSMTReplicator", () => {
149151
});
150152
});
151153

154+
describe("transitionRoot() with signature", () => {
155+
it("should transition root with valid signature from oracle", async () => {
156+
const randomRoot = ethers.hexlify(ethers.randomBytes(32));
157+
const currentTime = await time.latest();
158+
159+
const messageHash = ethers.keccak256(
160+
ethers.solidityPacked(
161+
["string", "address", "address", "bytes32", "uint256"],
162+
[REGISTRATION_ROOT_PREFIX, SOURCE_SMT.address, await replicator.getAddress(), randomRoot, currentTime],
163+
),
164+
);
165+
166+
const signature = await ORACLE1.signMessage(ethers.getBytes(messageHash));
167+
168+
await replicator.transitionRootWithSignature(randomRoot, currentTime, signature);
169+
170+
expect(await replicator.latestRoot()).to.equal(randomRoot);
171+
expect(await replicator.latestTimestamp()).to.equal(currentTime);
172+
expect(await replicator.isRootValid(randomRoot)).to.be.true;
173+
});
174+
175+
it("should revert when signature is from non-oracle", async () => {
176+
const randomRoot = ethers.hexlify(ethers.randomBytes(32));
177+
const currentTime = await time.latest();
178+
179+
const messageHash = ethers.keccak256(
180+
ethers.solidityPacked(
181+
["string", "address", "address", "bytes32", "uint256"],
182+
[REGISTRATION_ROOT_PREFIX, SOURCE_SMT.address, await replicator.getAddress(), randomRoot, currentTime],
183+
),
184+
);
185+
186+
const signature = await OTHER.signMessage(ethers.getBytes(messageHash));
187+
188+
await expect(
189+
replicator.transitionRootWithSignature(randomRoot, currentTime, signature),
190+
).to.be.revertedWithCustomError(replicator, "NotAnOracle");
191+
});
192+
});
193+
152194
describe("Upgradability", () => {
153195
it("should allow owner to upgrade", async () => {
154196
const newImplementation = await ethers.deployContract("RegistrationSMTReplicator");

0 commit comments

Comments
 (0)