refactor: store pubkeys as string

also add some comment code for registerPubkey function
This commit is contained in:
Evi Nova
2025-11-30 12:02:58 -03:00
parent 401c172371
commit 3cab856a07
5 changed files with 77 additions and 63 deletions

View File

@@ -27,6 +27,13 @@ interface IIdentityRegistrySelfricaV1 {
*/
function checkPubkeyCommitment(uint256 pubkeyCommitment) external view returns (bool);
/**
* @notice Checks if the provided pubkey string is stored in the registry.
* @param pubkey The pubkey string to verify.
* @return True if the pubkey is stored in the registry, false otherwise.
*/
function checkPubkey(string calldata pubkey) external view returns (bool);
/**
* @notice Checks if the identity commitment Merkle tree contains the specified root.
* @param root The Merkle tree root to check.

View File

@@ -25,44 +25,18 @@ library GCPJWTHelper {
return 0;
}
function extractPubkeyCommitment(uint256 p0, uint256 p1, uint256 p2) internal pure returns (uint256) {
function unpackPubkeyString(uint256 p0, uint256 p1, uint256 p2) internal pure returns (string memory) {
bytes memory b64 = new bytes(93);
uint256 idx;
for (; p0 > 0 && idx < 31; idx++) { b64[idx] = bytes1(uint8(p0 & 0xff)); p0 >>= 8; }
for (; p1 > 0 && idx < 62; idx++) { b64[idx] = bytes1(uint8(p1 & 0xff)); p1 >>= 8; }
for (; p2 > 0 && idx < 93; idx++) { b64[idx] = bytes1(uint8(p2 & 0xff)); p2 >>= 8; }
bytes memory decoded = _decodeBase64Url(b64, idx);
uint256 result;
for (uint256 i; i < decoded.length && i < 32; i++) {
result = (result << 8) | uint8(decoded[i]);
// Trim to actual length
bytes memory result = new bytes(idx);
for (uint256 i; i < idx; i++) {
result[i] = b64[i];
}
return result;
}
function _decodeBase64Url(bytes memory data, uint256 len) private pure returns (bytes memory) {
if (len == 0) return new bytes(0);
uint256 outLen = (len * 3) / 4;
bytes memory result = new bytes(outLen);
uint256 j;
for (uint256 i; i < len; i += 4) {
uint8 a = _b64(uint8(data[i]));
uint8 b = i + 1 < len ? _b64(uint8(data[i + 1])) : 0;
uint8 c = i + 2 < len ? _b64(uint8(data[i + 2])) : 0;
uint8 d = i + 3 < len ? _b64(uint8(data[i + 3])) : 0;
if (j < outLen) result[j++] = bytes1((a << 2) | (b >> 4));
if (j < outLen && i + 2 < len) result[j++] = bytes1((b << 4) | (c >> 2));
if (j < outLen && i + 3 < len) result[j++] = bytes1((c << 6) | d);
}
return result;
}
function _b64(uint8 c) private pure returns (uint8) {
if (c >= 65 && c <= 90) return c - 65;
if (c >= 97 && c <= 122) return c - 71;
if (c >= 48 && c <= 57) return c + 4;
if (c == 45) return 62;
if (c == 95) return 63;
return 0;
return string(result);
}
}

View File

@@ -67,6 +67,9 @@ abstract contract IdentityRegistrySelfricaStorageV1 is ImplRoot {
/// @notice Address of the GCP JWT verifier contract.
address internal _gcpJwtVerifier;
/// @notice Pubkey strings registered for Selfrica (via GCP JWT proof).
mapping(string => bool) internal _isRegisteredPubkey;
}
interface IGCPJWTVerifier {
@@ -110,10 +113,10 @@ contract IdentityRegistrySelfricaImplV1 is IdentityRegistrySelfricaStorageV1, II
uint256 imtRoot,
uint256 imtIndex
);
/// @notice Emitted when a public key commitment is successfully registered.
event PubkeyRegistered(uint256 indexed commitment);
/// @notice Emitted when a public key commitment is removed.
event PubkeyRemoved(uint256 indexed commitment);
/// @notice Emitted when a public key commitment is successfully registered (owner).
event PubkeyCommitmentRegistered(uint256 indexed commitment);
/// @notice Emitted when a public key is successfully registered (via GCP JWT proof).
event PubkeyRegistered(string pubkey);
/// @notice Emitted when a identity commitment is added by dev team.
event DevCommitmentRegistered(
@@ -222,13 +225,20 @@ contract IdentityRegistrySelfricaImplV1 is IdentityRegistrySelfricaStorageV1, II
return _rootTimestamps[root];
}
/// @notice Checks if the pubkey commitment is registered.
/// @notice Checks if the pubkey commitment is registered (owner-registered).
/// @param pubkeyCommitment The pubkey commitment to check.
/// @return True if the pubkey commitment is registered, false otherwise.
function isRegisteredPubkeyCommitment(uint256 pubkeyCommitment) external view onlyProxy returns (bool) {
return _isRegisteredPubkeyCommitment[pubkeyCommitment];
}
/// @notice Checks if the pubkey string is registered (via GCP JWT proof).
/// @param pubkey The pubkey string to check.
/// @return True if the pubkey is registered, false otherwise.
function isRegisteredPubkey(string calldata pubkey) external view onlyProxy returns (bool) {
return _isRegisteredPubkey[pubkey];
}
/// @notice Retrieves the total number of identity commitments in the Merkle tree.
/// @return The size (i.e., count) of the identity commitment Merkle tree.
function getIdentityCommitmentMerkleTreeSize() external view virtual onlyProxy returns (uint256) {
@@ -273,7 +283,7 @@ contract IdentityRegistrySelfricaImplV1 is IdentityRegistrySelfricaStorageV1, II
}
/**
* @notice Checks if the provided pubkey commitment is stored in the registry.
* @notice Checks if the provided pubkey commitment is stored in the registry (owner-registered).
* @param pubkeyCommitment The pubkey commitment to verify.
* @return True if the pubkey commitment is stored in the registry, false otherwise.
*/
@@ -281,6 +291,15 @@ contract IdentityRegistrySelfricaImplV1 is IdentityRegistrySelfricaStorageV1, II
return _isRegisteredPubkeyCommitment[pubkeyCommitment];
}
/**
* @notice Checks if the provided pubkey string is stored in the registry (via GCP JWT proof).
* @param pubkey The pubkey string to verify.
* @return True if the pubkey is stored in the registry, false otherwise.
*/
function checkPubkey(string calldata pubkey) external view onlyProxy returns (bool) {
return _isRegisteredPubkey[pubkey];
}
// ====================================================
// External Functions - Registration
// ====================================================
@@ -348,10 +367,10 @@ contract IdentityRegistrySelfricaImplV1 is IdentityRegistrySelfricaStorageV1, II
*/
function registerPubkeyCommitment(uint256 pubkeyCommitment) external virtual onlyProxy onlyOwner {
_isRegisteredPubkeyCommitment[pubkeyCommitment] = true;
emit PubkeyRegistered(pubkeyCommitment);
emit PubkeyCommitmentRegistered(pubkeyCommitment);
}
/// @notice Registers a pubkey commitment via GCP JWT proof.
/// @notice Registers a pubkey via GCP JWT proof.
/// @dev Verifies the proof, checks root CA hash matches constant, validates image hash against PCR0Manager.
/// @param pA Groth16 proof element A.
/// @param pB Groth16 proof element B.
@@ -363,15 +382,20 @@ contract IdentityRegistrySelfricaImplV1 is IdentityRegistrySelfricaStorageV1, II
uint256[2] calldata pC,
uint256[7] calldata pubSignals
) external onlyProxy {
// Check if the proof is valid
if (!IGCPJWTVerifier(_gcpJwtVerifier).verifyProof(pA, pB, pC, pubSignals)) revert INVALID_PROOF();
// Check if the root CA pubkey hash is valid
if (pubSignals[0] != GCP_ROOT_CA_PUBKEY_HASH) revert INVALID_ROOT_CA();
// Check if the TEE image hash is valid
bytes memory imageHash = GCPJWTHelper.unpackAndConvertImageHash(pubSignals[4], pubSignals[5], pubSignals[6]);
if (!IPCR0Manager(_PCR0Manager).isPCR0Set(imageHash)) revert INVALID_IMAGE();
uint256 pubkeyCommitment = GCPJWTHelper.extractPubkeyCommitment(pubSignals[1], pubSignals[2], pubSignals[3]);
_isRegisteredPubkeyCommitment[pubkeyCommitment] = true;
emit PubkeyRegistered(pubkeyCommitment);
// Unpack the pubkey and register it
string memory pubkey = GCPJWTHelper.unpackPubkeyString(pubSignals[1], pubSignals[2], pubSignals[3]);
_isRegisteredPubkey[pubkey] = true;
emit PubkeyRegistered(pubkey);
}
/// @notice Updates the GCP JWT verifier contract address.

View File

@@ -24,18 +24,18 @@ contract TestGCPJWTHelper {
}
/**
* @notice Exposes extractPubkeyCommitment for testing
* @notice Exposes unpackPubkeyString for testing
* @param p0 First packed field element (up to 31 bytes of base64url chars)
* @param p1 Second packed field element (up to 31 bytes of base64url chars)
* @param p2 Third packed field element (remaining base64url chars)
* @return The extracted pubkey commitment as uint256
* @return The unpacked pubkey as a string
*/
function testExtractPubkeyCommitment(
function testUnpackPubkeyString(
uint256 p0,
uint256 p1,
uint256 p2
) external pure returns (uint256) {
return GCPJWTHelper.extractPubkeyCommitment(p0, p1, p2);
) external pure returns (string memory) {
return GCPJWTHelper.unpackPubkeyString(p0, p1, p2);
}
/**

View File

@@ -116,7 +116,7 @@ describe("GCPJWTHelper", function () {
});
});
describe("extractPubkeyCommitment", function () {
describe("unpackPubkeyString", function () {
// Known test vectors from TypeScript implementation
// These values pack to the base64url string: "AmtPnrcj3vuhOo10QXjKfsQ2JZsLt7DqeeTHyLlicfUe"
const testPubkey = {
@@ -125,34 +125,34 @@ describe("GCPJWTHelper", function () {
p2: 0n,
};
// Expected pubkey commitment (from TypeScript verification)
const expectedCommitment = 1094227850017695624326559586424504982480957966087397748000597471445507011061n;
// Expected pubkey string (base64url encoded)
const expectedPubkeyString = "AmtPnrcj3vuhOo10QXjKfsQ2JZsLt7DqeeTHyLlicfUe";
it("should correctly decode base64url and extract commitment", async function () {
const result = await testHelper.testExtractPubkeyCommitment(
it("should correctly unpack to the expected string", async function () {
const result = await testHelper.testUnpackPubkeyString(
testPubkey.p0,
testPubkey.p1,
testPubkey.p2,
);
expect(result).to.equal(expectedCommitment);
expect(result).to.equal(expectedPubkeyString);
});
it("should handle zeros correctly", async function () {
// All zeros should produce zero output
const result = await testHelper.testExtractPubkeyCommitment(0n, 0n, 0n);
expect(result).to.equal(0n);
// All zeros should produce empty string
const result = await testHelper.testUnpackPubkeyString(0n, 0n, 0n);
expect(result).to.equal("");
});
it("should produce consistent results", async function () {
// Call multiple times to ensure deterministic behavior
const result1 = await testHelper.testExtractPubkeyCommitment(
const result1 = await testHelper.testUnpackPubkeyString(
testPubkey.p0,
testPubkey.p1,
testPubkey.p2,
);
const result2 = await testHelper.testExtractPubkeyCommitment(
const result2 = await testHelper.testUnpackPubkeyString(
testPubkey.p0,
testPubkey.p1,
testPubkey.p2,
@@ -166,15 +166,24 @@ describe("GCPJWTHelper", function () {
// Uppercase: A-Z
// Lowercase: a-z
// Numbers: 0-9
// Special: - and _ (though not in this particular test string)
// The fact that we get the correct commitment proves all chars are decoded correctly
const result = await testHelper.testExtractPubkeyCommitment(
// The fact that we get the correct string proves all chars are unpacked correctly
const result = await testHelper.testUnpackPubkeyString(
testPubkey.p0,
testPubkey.p1,
testPubkey.p2,
);
expect(result).to.equal(expectedCommitment);
expect(result).to.equal(expectedPubkeyString);
});
it("should return correct length string", async function () {
const result = await testHelper.testUnpackPubkeyString(
testPubkey.p0,
testPubkey.p1,
testPubkey.p2,
);
expect(result.length).to.equal(expectedPubkeyString.length);
});
});