mirror of
https://github.com/selfxyz/self.git
synced 2026-04-27 03:01:15 -04:00
* Update dependency versions * Fix gesture handler Android dependency (#1611) * Patch screens codegen type (#1609) * Downgrade Sentry React Native (#1612) * fix patches and packages * downgrade versions for gesture handler and screens * agent feedback * fix ios building * allow ios tets to pass * formatting * make cache more resilient * Address CodeRabbitAI review comments This commit addresses all 7 unresolved CodeRabbitAI comments on PR #1606: Patch-package error handling (comments #1, #2, #3): - stderr capture already implemented in both root and workspace patch runs - Add CI warning when patches fail silently instead of exiting with 0 - Log completion status in CI mode for visibility Critical Mixpanel dependency fix (comment #5): - Add explicit Mixpanel-swift pod declaration to fix E2E build failures - Ensures Mixpanel is available even when NFCPassportReader is skipped during E2E testing React-native-web validation (comment #4): - Verified no usage of deprecated findNodeHandle, pointerEvents: 'box-none', or createPortal - Safe to upgrade from 0.19 to 0.21.2 CI workflow improvements (comments #6, #7): - Create cache-sdk-build composite action for consistent SDK build artifact caching - Replace all direct actions/cache@v4 usage with cache-yarn composite action - Replace all direct actions/cache/restore@v4 and save@v4 with cache-sdk-build - Add nested require() validation step before tests to fail fast on problematic patterns All changes follow repository coding guidelines for CI caching and test memory optimization. * Extend cache composite actions to all SDK workflows This commit extends the caching standardization from PR #1606 to include mobile-sdk-ci.yml and core-sdk-ci.yml workflows. New composite actions created: - cache-mobile-sdk-build: For mobile SDK build artifacts - cache-core-sdk-build: For core SDK build artifacts Workflow updates: - mobile-sdk-ci.yml: Replaced 5 instances of direct actions/cache with cache-mobile-sdk-build - core-sdk-ci.yml: Replaced 4 instances of direct actions/cache with cache-core-sdk-build All SDK CI workflows now use consistent caching patterns via composite actions, following the AGENTS.md guideline: "Use shared composite actions from .github/actions for CI caching instead of calling actions/cache directly." Benefits: - Consistent caching across all SDK workflows (qrcode, mobile, core) - Centralized cache configuration - easier to maintain - Follows established patterns from qrcode-sdk-ci.yml * downgrade react-native-svg * update pod lock file * sort
467 lines
18 KiB
TypeScript
467 lines
18 KiB
TypeScript
import { ethers } from "hardhat";
|
|
import { deploySystemFixturesV2 } from "../utils/deploymentV2";
|
|
import { DeployedActorsV2 } from "../utils/types";
|
|
import { KYC_ATTESTATION_ID } from "@selfxyz/common/constants/constants";
|
|
import { generateMockKycRegisterInput } from "@selfxyz/common/utils/kyc/generateInputs";
|
|
import { generateRegisterKycProof } from "../utils/generateProof";
|
|
import { expect } from "chai";
|
|
|
|
function getCurrentDateDigitsYYMMDDHHMMSS(hoursOffset: number = 0): bigint[] {
|
|
const now = new Date();
|
|
if (hoursOffset !== 0) {
|
|
now.setUTCHours(now.getUTCHours() + hoursOffset);
|
|
}
|
|
const pad2 = (n: number) => n.toString().padStart(2, "0");
|
|
const yy = pad2(now.getUTCFullYear() % 100);
|
|
const mm = pad2(now.getUTCMonth() + 1);
|
|
const dd = pad2(now.getUTCDate());
|
|
const hh = pad2(now.getUTCHours());
|
|
const min = pad2(now.getUTCMinutes());
|
|
const ss = pad2(now.getUTCSeconds());
|
|
return `${yy}${mm}${dd}${hh}${min}${ss}`.split("").map(Number).map(BigInt);
|
|
}
|
|
|
|
/**
|
|
* Packs a uint256 value into field elements as a 64-character hex string.
|
|
* This mirrors how the GCP JWT circuit outputs pubkey commitments.
|
|
*/
|
|
function packUint256ToHexFields(value: bigint): [bigint, bigint, bigint] {
|
|
const hexStr = value.toString(16).padStart(64, "0");
|
|
const bytes = Buffer.from(hexStr, "utf8");
|
|
|
|
let p0 = 0n,
|
|
p1 = 0n,
|
|
p2 = 0n;
|
|
|
|
for (let i = 0; i < Math.min(31, bytes.length); i++) {
|
|
p0 |= BigInt(bytes[i]) << BigInt(i * 8);
|
|
}
|
|
for (let i = 31; i < Math.min(62, bytes.length); i++) {
|
|
p1 |= BigInt(bytes[i]) << BigInt((i - 31) * 8);
|
|
}
|
|
for (let i = 62; i < Math.min(93, bytes.length); i++) {
|
|
p2 |= BigInt(bytes[i]) << BigInt((i - 62) * 8);
|
|
}
|
|
|
|
return [p0, p1, p2];
|
|
}
|
|
|
|
describe("KYC Registration test", function () {
|
|
this.timeout(0);
|
|
|
|
let deployedActors: DeployedActorsV2;
|
|
let snapshotId: string;
|
|
let attestationIdBytes32: string;
|
|
|
|
const GCP_ROOT_CA_PUBKEY_HASH = 21107503781769611051785921462832133421817512022858926231578334326320168810501n;
|
|
|
|
before(async () => {
|
|
deployedActors = await deploySystemFixturesV2();
|
|
attestationIdBytes32 = ethers.zeroPadValue(ethers.toBeHex(BigInt(KYC_ATTESTATION_ID)), 32);
|
|
|
|
// Set the owner as the TEE for all tests
|
|
await deployedActors.registryKyc.updateTEE(await deployedActors.owner.getAddress());
|
|
|
|
// Set the GCP root CA pubkey hash
|
|
await deployedActors.registryKyc.updateGCPRootCAPubkeyHash(GCP_ROOT_CA_PUBKEY_HASH);
|
|
|
|
console.log("🎉 System deployment and initial setup completed!");
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
snapshotId = await ethers.provider.send("evm_snapshot", []);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await ethers.provider.send("evm_revert", [snapshotId]);
|
|
});
|
|
|
|
describe("Identity Commitment", () => {
|
|
let kycData: any;
|
|
let registerProof: any;
|
|
let registerSecret: string;
|
|
let mockVerifier: any;
|
|
let mockProof: any;
|
|
let mockPubSignals: bigint[];
|
|
let snapshotId: string;
|
|
|
|
before(async () => {
|
|
registerSecret = "12345";
|
|
kycData = await generateMockKycRegisterInput(undefined, true, registerSecret);
|
|
registerProof = await generateRegisterKycProof(registerSecret, kycData);
|
|
|
|
// Deploy and set mock GCP JWT verifier
|
|
const MockVerifierFactory = await ethers.getContractFactory("MockGCPJWTVerifier");
|
|
mockVerifier = await MockVerifierFactory.deploy();
|
|
await mockVerifier.waitForDeployment();
|
|
await deployedActors.registryKyc.updateGCPJWTVerifier(mockVerifier.target);
|
|
|
|
// Get the pubkey commitment from the register proof and pack as hex
|
|
const pubkeyCommitment = registerProof.pubSignals[registerProof.pubSignals.length - 2];
|
|
const [p0, p1, p2] = packUint256ToHexFields(BigInt(pubkeyCommitment));
|
|
|
|
// Test image hash that unpacks to: d2221a0ee83901980c607ceff2edbedf3f6ce5f437eafa5d89be39e9e7487c04
|
|
const testImageHash = {
|
|
p0: 177384435506496807268973340845468654286294928521500580044819492874465981028n,
|
|
p1: 175298970718174405520284770870231222447414486446296682893283627688949855078n,
|
|
p2: 13360n,
|
|
};
|
|
|
|
// Add the corresponding PCR0 (16 zero bytes + 32 hash bytes)
|
|
const pcr0Bytes = ethers.getBytes(
|
|
"0x" + "d2221a0ee83901980c607ceff2edbedf3f6ce5f437eafa5d89be39e9e7487c04".padStart(32, "0"),
|
|
);
|
|
await deployedActors.pcr0Manager.addPCR0(pcr0Bytes);
|
|
|
|
// Register the pubkey commitment via GCP JWT proof
|
|
mockProof = {
|
|
a: [1n, 2n] as [bigint, bigint],
|
|
b: [
|
|
[1n, 2n],
|
|
[3n, 4n],
|
|
] as [[bigint, bigint], [bigint, bigint]],
|
|
c: [1n, 2n] as [bigint, bigint],
|
|
};
|
|
|
|
mockPubSignals = [
|
|
GCP_ROOT_CA_PUBKEY_HASH,
|
|
p0,
|
|
p1,
|
|
p2,
|
|
testImageHash.p0,
|
|
testImageHash.p1,
|
|
testImageHash.p2,
|
|
...getCurrentDateDigitsYYMMDDHHMMSS(),
|
|
];
|
|
// Take an EVM snapshot before tests to allow reverting in each test for isolation
|
|
// We will use this snapshot in the afterEach for revert, but store it here.
|
|
// Using Mocha "this" context to store snapshotId for this suite.
|
|
snapshotId = await ethers.provider.send("evm_snapshot", []);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await ethers.provider.send("evm_revert", [snapshotId]);
|
|
snapshotId = await ethers.provider.send("evm_snapshot", []);
|
|
});
|
|
|
|
it("should successfully register an identity commitment", async () => {
|
|
await deployedActors.registryKyc.registerPubkeyCommitment(mockProof.a, mockProof.b, mockProof.c, mockPubSignals);
|
|
|
|
await expect(deployedActors.hub.registerCommitment(attestationIdBytes32, 0n, registerProof)).to.emit(
|
|
deployedActors.registryKyc,
|
|
"CommitmentRegistered",
|
|
);
|
|
|
|
const isRegistered = await deployedActors.registryKyc.nullifiers(registerProof.pubSignals[0]);
|
|
expect(isRegistered).to.be.true;
|
|
});
|
|
|
|
it("should throw an error if the pubkey commitment is not registered", async () => {
|
|
await expect(
|
|
deployedActors.hub.registerCommitment(attestationIdBytes32, 0n, registerProof),
|
|
).to.be.revertedWithCustomError(deployedActors.hub, "InvalidPubkeyCommitment");
|
|
});
|
|
|
|
it("should not register an identity commitment if the proof is invalid", async () => {
|
|
await deployedActors.registryKyc.registerPubkeyCommitment(mockProof.a, mockProof.b, mockProof.c, mockPubSignals);
|
|
|
|
const invalidRegisterProof = structuredClone(registerProof);
|
|
invalidRegisterProof.pubSignals[1] = 0n;
|
|
await expect(
|
|
deployedActors.hub.registerCommitment(attestationIdBytes32, 0n, invalidRegisterProof),
|
|
).to.be.revertedWithCustomError(deployedActors.hub, "InvalidRegisterProof");
|
|
});
|
|
|
|
it("should fail with NoVerifierSet when using non-existent register verifier ID", async () => {
|
|
await expect(
|
|
deployedActors.hub.registerCommitment(attestationIdBytes32, 999999n, registerProof),
|
|
).to.be.revertedWithCustomError(deployedActors.hub, "NoVerifierSet");
|
|
});
|
|
|
|
it("should fail with NoVerifierSet when attestation ID is invalid", async () => {
|
|
const invalidAttestationId = ethers.zeroPadValue(ethers.toBeHex(999), 32);
|
|
await expect(
|
|
deployedActors.hub.registerCommitment(invalidAttestationId, 0n, registerProof),
|
|
).to.be.revertedWithCustomError(deployedActors.hub, "NoVerifierSet");
|
|
});
|
|
|
|
it("should fail with InvalidAttestationId for mismatched verifier registry", async () => {
|
|
const invalidAttestationId = ethers.zeroPadValue(ethers.toBeHex(999), 32);
|
|
await deployedActors.hub.updateRegisterCircuitVerifier(
|
|
invalidAttestationId,
|
|
1n,
|
|
await deployedActors.registryAadhaar.getAddress(),
|
|
);
|
|
|
|
await expect(
|
|
deployedActors.hub.registerCommitment(invalidAttestationId, 1n, registerProof),
|
|
).to.be.revertedWithCustomError(deployedActors.hub, "InvalidAttestationId");
|
|
});
|
|
|
|
it("should fail with InvalidPubkeyCommitment when pubkey commitment is not registered", async () => {
|
|
const newRegisterProof = structuredClone(registerProof);
|
|
newRegisterProof.pubSignals[2] = 0n;
|
|
|
|
await expect(
|
|
deployedActors.hub.registerCommitment(attestationIdBytes32, 0n, newRegisterProof),
|
|
).to.be.revertedWithCustomError(deployedActors.hub, "InvalidPubkeyCommitment");
|
|
});
|
|
});
|
|
|
|
describe("GCP JWT Pubkey Registration", () => {
|
|
const mockProof = {
|
|
a: [1n, 2n] as [bigint, bigint],
|
|
b: [
|
|
[1n, 2n],
|
|
[3n, 4n],
|
|
] as [[bigint, bigint], [bigint, bigint]],
|
|
c: [1n, 2n] as [bigint, bigint],
|
|
};
|
|
|
|
it("should have correct GCP root CA pubkey hash", async () => {
|
|
const contractHash = await deployedActors.registryKyc.gcpRootCAPubkeyHash();
|
|
expect(contractHash).to.equal(GCP_ROOT_CA_PUBKEY_HASH);
|
|
});
|
|
|
|
it("should allow owner to update GCP root CA pubkey hash", async () => {
|
|
const newHash = 12345n;
|
|
await deployedActors.registryKyc.updateGCPRootCAPubkeyHash(newHash);
|
|
const contractHash = await deployedActors.registryKyc.gcpRootCAPubkeyHash();
|
|
expect(contractHash).to.equal(newHash);
|
|
});
|
|
|
|
it("should not allow non-owner to update GCP root CA pubkey hash", async () => {
|
|
await expect(
|
|
deployedActors.registryKyc.connect(deployedActors.user1).updateGCPRootCAPubkeyHash(12345n),
|
|
).to.be.revertedWithCustomError(deployedActors.registryKyc, "AccessControlUnauthorizedAccount");
|
|
});
|
|
|
|
it("should fail with INVALID_IMAGE when image hash not in PCR0Manager", async () => {
|
|
const mockPubSignals: bigint[] = [
|
|
GCP_ROOT_CA_PUBKEY_HASH,
|
|
1n,
|
|
2n,
|
|
3n,
|
|
4n,
|
|
5n,
|
|
6n,
|
|
...getCurrentDateDigitsYYMMDDHHMMSS().map(BigInt),
|
|
];
|
|
|
|
await expect(
|
|
deployedActors.registryKyc.registerPubkeyCommitment(mockProof.a, mockProof.b, mockProof.c, mockPubSignals),
|
|
).to.be.revertedWithCustomError(deployedActors.registryKyc, "INVALID_IMAGE");
|
|
});
|
|
|
|
it("should not allow non-owner to update GCP JWT verifier", async () => {
|
|
await expect(
|
|
deployedActors.registryKyc
|
|
.connect(deployedActors.user1)
|
|
.updateGCPJWTVerifier(ethers.Wallet.createRandom().address),
|
|
).to.be.revertedWithCustomError(deployedActors.registryKyc, "AccessControlUnauthorizedAccount");
|
|
});
|
|
|
|
it("should allow owner to update GCP JWT verifier", async () => {
|
|
const newVerifier = ethers.Wallet.createRandom().address;
|
|
await deployedActors.registryKyc.updateGCPJWTVerifier(newVerifier);
|
|
});
|
|
|
|
describe("TEE Access Control", () => {
|
|
it("should not allow non-TEE to register pubkey commitment", async () => {
|
|
const mockPubSignals: bigint[] = [
|
|
GCP_ROOT_CA_PUBKEY_HASH,
|
|
1n,
|
|
2n,
|
|
3n,
|
|
4n,
|
|
5n,
|
|
6n,
|
|
...getCurrentDateDigitsYYMMDDHHMMSS().map(BigInt),
|
|
];
|
|
|
|
await expect(
|
|
deployedActors.registryKyc
|
|
.connect(deployedActors.user1)
|
|
.registerPubkeyCommitment(mockProof.a, mockProof.b, mockProof.c, mockPubSignals),
|
|
).to.be.revertedWithCustomError(deployedActors.registryKyc, "ONLY_TEE_CAN_ACCESS");
|
|
});
|
|
|
|
it("should not allow non-owner to update TEE", async () => {
|
|
await expect(
|
|
deployedActors.registryKyc.connect(deployedActors.user1).updateTEE(ethers.Wallet.createRandom().address),
|
|
).to.be.revertedWithCustomError(deployedActors.registryKyc, "AccessControlUnauthorizedAccount");
|
|
});
|
|
|
|
it("should allow owner to update TEE", async () => {
|
|
const newTee = ethers.Wallet.createRandom().address;
|
|
await deployedActors.registryKyc.updateTEE(newTee);
|
|
expect(await deployedActors.registryKyc.tee()).to.equal(newTee);
|
|
});
|
|
|
|
it("should fail with TEE_NOT_SET when TEE address is zero", async () => {
|
|
// Deploy minimal fresh registry to test uninitialized TEE state
|
|
const freshImpl = await (
|
|
await ethers.getContractFactory("IdentityRegistryKycImplV1", {
|
|
libraries: { PoseidonT3: deployedActors.poseidonT3.target },
|
|
})
|
|
).deploy();
|
|
|
|
const initData = freshImpl.interface.encodeFunctionData("initialize", [
|
|
ethers.ZeroAddress,
|
|
deployedActors.pcr0Manager.target,
|
|
]);
|
|
const freshProxy = await (
|
|
await ethers.getContractFactory("IdentityRegistry")
|
|
).deploy(freshImpl.target, initData);
|
|
|
|
const freshRegistry = await ethers.getContractAt("IdentityRegistryKycImplV1", freshProxy.target);
|
|
await freshRegistry.updateGCPJWTVerifier(deployedActors.gcpJwtVerifier.target);
|
|
|
|
const mockPubSignals: bigint[] = [
|
|
GCP_ROOT_CA_PUBKEY_HASH,
|
|
1n,
|
|
2n,
|
|
3n,
|
|
4n,
|
|
5n,
|
|
6n,
|
|
...getCurrentDateDigitsYYMMDDHHMMSS().map(BigInt),
|
|
];
|
|
|
|
await expect(
|
|
freshRegistry.registerPubkeyCommitment(mockProof.a, mockProof.b, mockProof.c, mockPubSignals),
|
|
).to.be.revertedWithCustomError(freshRegistry, "TEE_NOT_SET");
|
|
});
|
|
|
|
it("should fail with INVALID_TIMESTAMP when timestamp is in the past or future", async () => {
|
|
// Add the PCR0 image hash so the image validation passes and we can test timestamp validation
|
|
// addPCR0 takes 32 bytes and pads to 48 bytes internally, isPCR0Set requires 48 bytes
|
|
const pcr0Hash = "d2221a0ee83901980c607ceff2edbedf3f6ce5f437eafa5d89be39e9e7487c04";
|
|
const pcr0Bytes32 = ethers.getBytes("0x" + pcr0Hash);
|
|
const pcr0Bytes48 = ethers.getBytes("0x" + "00".repeat(16) + pcr0Hash);
|
|
// Only add PCR0 if not already set (may have been added by earlier test)
|
|
const isAlreadySet = await deployedActors.pcr0Manager.isPCR0Set(pcr0Bytes48);
|
|
if (!isAlreadySet) {
|
|
await deployedActors.pcr0Manager.addPCR0(pcr0Bytes32);
|
|
}
|
|
|
|
let mockPubkeyCommitment = 12345678901234567890123456789012n;
|
|
const [p0, p1, p2] = packUint256ToHexFields(BigInt(mockPubkeyCommitment));
|
|
|
|
// Create a timestamp 2 hours in the past (more than 1 hour threshold)
|
|
const previousHourDate = getCurrentDateDigitsYYMMDDHHMMSS(-2);
|
|
|
|
const mockPubSignalsPast = [
|
|
GCP_ROOT_CA_PUBKEY_HASH,
|
|
p0,
|
|
p1,
|
|
p2,
|
|
177384435506496807268973340845468654286294928521500580044819492874465981028n,
|
|
175298970718174405520284770870231222447414486446296682893283627688949855078n,
|
|
13360n,
|
|
...previousHourDate,
|
|
];
|
|
|
|
await expect(
|
|
deployedActors.registryKyc.registerPubkeyCommitment(
|
|
mockProof.a,
|
|
mockProof.b,
|
|
mockProof.c,
|
|
mockPubSignalsPast,
|
|
),
|
|
).to.be.revertedWithCustomError(deployedActors.registryKyc, "INVALID_TIMESTAMP");
|
|
|
|
// Create a timestamp 2 hours in the future (more than 1 hour threshold)
|
|
const nextHourDate = getCurrentDateDigitsYYMMDDHHMMSS(2);
|
|
|
|
const mockPubSignalsFuture = [
|
|
GCP_ROOT_CA_PUBKEY_HASH,
|
|
p0,
|
|
p1,
|
|
p2,
|
|
177384435506496807268973340845468654286294928521500580044819492874465981028n,
|
|
175298970718174405520284770870231222447414486446296682893283627688949855078n,
|
|
13360n,
|
|
...nextHourDate,
|
|
];
|
|
|
|
await expect(
|
|
deployedActors.registryKyc.registerPubkeyCommitment(
|
|
mockProof.a,
|
|
mockProof.b,
|
|
mockProof.c,
|
|
mockPubSignalsFuture,
|
|
),
|
|
).to.be.revertedWithCustomError(deployedActors.registryKyc, "INVALID_TIMESTAMP");
|
|
});
|
|
});
|
|
|
|
describe("with MockGCPJWTVerifier", () => {
|
|
let mockVerifier: any;
|
|
|
|
before(async () => {
|
|
const MockVerifierFactory = await ethers.getContractFactory("MockGCPJWTVerifier");
|
|
mockVerifier = await MockVerifierFactory.deploy();
|
|
await mockVerifier.waitForDeployment();
|
|
await deployedActors.registryKyc.updateGCPJWTVerifier(mockVerifier.target);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await mockVerifier.setShouldVerify(true);
|
|
});
|
|
|
|
it("should fail with INVALID_PROOF when verifier rejects proof", async () => {
|
|
await mockVerifier.setShouldVerify(false);
|
|
const mockPubSignals: bigint[] = [
|
|
GCP_ROOT_CA_PUBKEY_HASH,
|
|
1n,
|
|
2n,
|
|
3n,
|
|
4n,
|
|
5n,
|
|
6n,
|
|
...getCurrentDateDigitsYYMMDDHHMMSS().map(BigInt),
|
|
];
|
|
|
|
await expect(
|
|
deployedActors.registryKyc.registerPubkeyCommitment(mockProof.a, mockProof.b, mockProof.c, mockPubSignals),
|
|
).to.be.revertedWithCustomError(deployedActors.registryKyc, "INVALID_PROOF");
|
|
});
|
|
|
|
it("should fail with INVALID_ROOT_CA when root CA hash does not match", async () => {
|
|
const mockPubSignals: bigint[] = [
|
|
12345n,
|
|
1n,
|
|
2n,
|
|
3n,
|
|
4n,
|
|
5n,
|
|
6n,
|
|
...getCurrentDateDigitsYYMMDDHHMMSS().map(BigInt),
|
|
];
|
|
|
|
await expect(
|
|
deployedActors.registryKyc.registerPubkeyCommitment(mockProof.a, mockProof.b, mockProof.c, mockPubSignals),
|
|
).to.be.revertedWithCustomError(deployedActors.registryKyc, "INVALID_ROOT_CA");
|
|
});
|
|
|
|
it("should fail with INVALID_IMAGE when image hash not in PCR0Manager", async () => {
|
|
const mockPubSignals: bigint[] = [
|
|
GCP_ROOT_CA_PUBKEY_HASH,
|
|
1n,
|
|
2n,
|
|
3n,
|
|
4n,
|
|
5n,
|
|
6n,
|
|
...getCurrentDateDigitsYYMMDDHHMMSS().map(BigInt),
|
|
];
|
|
|
|
await expect(
|
|
deployedActors.registryKyc.registerPubkeyCommitment(mockProof.a, mockProof.b, mockProof.c, mockPubSignals),
|
|
).to.be.revertedWithCustomError(deployedActors.registryKyc, "INVALID_IMAGE");
|
|
});
|
|
});
|
|
});
|
|
});
|