diff --git a/README.md b/README.md index 903908f..20390b5 100644 --- a/README.md +++ b/README.md @@ -72,13 +72,14 @@ This documentation will guide you through: For unauthorized access, the API on Gnosis Mainnet is rate limited with these limits per endpoint and remote ip: - - `/register_identity` 5 requests per 24 hours - - `/compile_event_trigger_definition` 20 requests per 24 hours - - `/register_event_identity` 5 requests per 24 hours - - `/get_event_trigger_expiration_block` 20 requests per 24 hours - - `/get_data_for_encryption` 10 requests per 24 hours - - `/get_decryption_key` 20 requests per 24 hours - - `/get_event_decryption_key` 20 requests per 24 hours + - `/time/register_identity` 5 requests per 24 hours + - `/time/get_data_for_encryption` 10 requests per 24 hours + - `/time/get_decryption_key` 20 requests per 24 hours + - `/event/compile_trigger_definition` 20 requests per 24 hours + - `/event/register_identity` 5 requests per 24 hours + - `/event/get_data_for_encryption` 10 requests per 24 hours + - `/event/get_trigger_expiration_block` 20 requests per 24 hours + - `/event/get_decryption_key` 20 requests per 24 hours - `/decrypt_commitment` 10 requests per 24 hours We recommend using Chiado for development, because there are no rate limits in place. @@ -87,13 +88,14 @@ If you need higher limits, contact [loring@brainbot.com](mailto:loring@brainbot. Authorized requests have these limits: - - `/register_identity` 500 requests per 24 hours - - `/compile_event_trigger_definition` 2000 requests per 24 hours - - `/register_event_identity` 500 requests per 24 hours - - `/get_event_trigger_expiration_block` 2000 requests per 24 hours - - `/get_data_for_encryption` 1000 requests per 24 hours - - `/get_decryption_key` 2000 requests per 24 hours - - `/get_event_decryption_key` 2000 requests per 24 hours + - `/time/register_identity` 500 requests per 24 hours + - `/time/get_data_for_encryption` 1000 requests per 24 hours + - `/time/get_decryption_key` 2000 requests per 24 hours + - `/event/compile_trigger_definition` 2000 requests per 24 hours + - `/event/register_identity` 500 requests per 24 hours + - `/event/get_data_for_encryption` 1000 requests per 24 hours + - `/event/get_trigger_expiration_block` 2000 requests per 24 hours + - `/event/get_decryption_key` 2000 requests per 24 hours - `/decrypt_commitment` 1000 requests per 24 hours Authorization is done by using an `Authorization: Bearer $API_KEY` header, when calling the API. @@ -110,14 +112,14 @@ Use the `/check_authentication` endpoint, to test your API key. To begin using the Shutter system, register an identity and specify a time-based decryption trigger. This step links an identity to a decryption key and sets the release conditions for the key to a Unix timestamp. -Refer to the `/register_identity` endpoint in the Swagger documentation for details on parameters and responses. +Refer to the `/time/register_identity` endpoint in the Swagger documentation for details on parameters and responses. > **Note**: When registering identities through our API, the API account address is used to compute the identity that will be returned. If you want to use your own address, you need to submit the registration directly to the registry contract. The contract's definition can be found here: > [ShutterRegistry.sol](https://github.com/shutter-network/contracts/blob/main/src/shutter-service/ShutterRegistry.sol#L1C1-L86C2). #### Example Request ```bash -curl -X POST https:///register_identity \ +curl -X POST https:///time/register_identity \ -H "Content-Type: application/json" \ -d '{ "decryptionTimestamp": 1735044061, @@ -144,11 +146,11 @@ Before registering an identity with event-based decryption triggers, you need to The trigger condition is specified by a `contract address` (mandatory), the event's signature (mandatory), and a number of additional arguments. Event data can be matched as `byte-equals` or numeric comparisons (`lt, lte, eq, gte, gt`) over an uint256-cast of the specified event data fields. -Refer to the `/compile_event_trigger_definition` endpoint in the Swagger documentation for details on parameters and responses. +Refer to the `/event/compile_trigger_definition` endpoint in the Swagger documentation for details on parameters and responses. #### Example Request ```bash -curl -X POST https:///compile_event_trigger_definition \ +curl -X POST https:///event/compile_trigger_definition \ -H "Content-Type: application/json" \ -d '{ "contract": "0x3465a347342B72BCf800aBf814324ba4a803c32b", @@ -185,20 +187,22 @@ curl -X POST https:///compile_event_trigger_definition \ ### 1.C Register an Identity with Event-based Decryption Triggers -In order to register, you need a compiled event trigger definition (created using `/compile_event_trigger_definition`, see above). Registered event-based decryption triggers are bound by a time-to-live (`ttl`). The decryption keys are only released once and only if: +An alternative to time-based decryption triggers is "event-based" decryption triggers. This is very similar to the time-based release conditions discussed above. However, here the decryption key is produced only when a specific EVM event has been observed by the keypers. + +The trigger condition is specified by a compiled event trigger definition (created using `/event/compile_trigger_definition`, see above). Registered event-based decryption triggers are bound by a time-to-live (`ttl`). The decryption keys are only released once and only if: - the release condition has not been met before (since registration) - the `ttl` timer has not run out, and - *all* conditions of the trigger definition were fulfilled. -Refer to the `/register_event_identity` endpoint in the Swagger documentation for details on parameters and responses. +Refer to the `/event/register_identity` endpoint in the Swagger documentation for details on parameters and responses. > **Note**: When registering identities through our API, the API account address is used to compute the identity that will be returned. For the time being, it is **not** possibly to register event based decryption triggers directly with the contract. The contract's definition can be found here: > [ShutterEventTriggerRegistry.sol](https://github.com/shutter-network/contracts/blob/main/src/shutter-service/ShutterEventTriggerRegistry.sol#L35-L40) #### Example Request ```bash -curl -X POST https:///register_event_identity \ +curl -X POST https:///event/register_identity \ -H "Content-Type: application/json" \ -d '{ "triggerDefinition": "0x01f86694953a0425accee2e05f22e78999c595ed2ee7183cf84fe480e205a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efe401e205a0000000000000000000000000812a6755975485c6e340f97de6790b34a94d1430c404c20402", @@ -220,17 +224,17 @@ curl -X POST https:///register_event_identity \ } ``` -> **Note**: The encoding of `triggerDefinition` is specified [in rolling-shutter](https://github.com/shutter-network/rolling-shutter/blob/main/docs/event.md). It is a concatenation of contract address, topic0 and the RLP encoding of the other conditions. Event definitions should be constructed using the `/compile_event_trigger_definition` endpoint. +> **Note**: The encoding of `triggerDefinition` is specified [in rolling-shutter](https://github.com/shutter-network/rolling-shutter/blob/main/docs/event.md). It is a concatenation of contract address, topic0 and the RLP encoding of the other conditions. Event definitions should be constructed using the `/event/compile_trigger_definition` endpoint. ### 1.D Get Event Trigger Identity Registration Expiration Block Retrieve the expiration block number for a given event trigger identity registration. This endpoint allows you to check the expiration block number for an event-based identity registration. -Refer to the `/get_event_trigger_expiration_block` endpoint in the Swagger documentation for details on parameters and responses. +Refer to the `/event/get_trigger_expiration_block` endpoint in the Swagger documentation for details on parameters and responses. #### Example Request ```bash -curl -X GET "https:///get_event_trigger_expiration_block?eon=1&identityPrefix=0x32fdbd2ca52e171f77db2757ff6200cd8446350f927a3ad46c0565483dd8b41c" +curl -X GET "https:///event/get_trigger_expiration_block?eon=1&identityPrefix=0x32fdbd2ca52e171f77db2757ff6200cd8446350f927a3ad46c0565483dd8b41c" ``` #### Example Response @@ -246,22 +250,21 @@ curl -X GET "https:///get_event_trigger_expiration_block?eon=1&ide #### 2.A Retrieve the Encryption Data -To encrypt commitments, obtain the encryption data associated with your identity. Use the `/get_data_for_encryption` endpoint to retrieve all necessary encryption data. +To encrypt commitments, obtain the encryption data associated with your identity. There are two endpoints: -This endpoint supports both time-based and event-based identity computation: -- For **Time-based**: Omit the `triggerDefinition` parameter. -- For **Event-based**: Provide the `triggerDefinition` parameter (hex-encoded with 0x prefix) as well to compute identity for event-based triggers. +- **Time-based**: `/time/get_data_for_encryption` — parameters `address` (required) and `identityPrefix` (optional). Use the address that will register the identity (your account if self-registering, or the API address: Gnosis `0x228DefCF37Da29475F0EE2B9E4dfAeDc3b0746bc`, Chiado `0xb9C303443c9af84777e60D5C987AbF0c43844918`). +- **Event-based**: `/event/get_data_for_encryption` — parameters `triggerDefinition` (required) and `identityPrefix` (optional). -Refer to the Swagger documentation for specifics on this endpoint. +Refer to the Swagger documentation for specifics on these endpoints. #### Example Request (Time-based) ```bash -curl -X GET "https:///get_data_for_encryption?address=0xb9C303443c9af84777e60D5C987AbF0c43844918&identityPrefix=0x79bc8f6b4fcb02c651d6a702b7ad965c7fca19e94a9646d21ae90c8b54c030a0" +curl -X GET "https:///time/get_data_for_encryption?address=0xb9C303443c9af84777e60D5C987AbF0c43844918&identityPrefix=0x79bc8f6b4fcb02c651d6a702b7ad965c7fca19e94a9646d21ae90c8b54c030a0" ``` #### Example Request (Event-based) ```bash -curl -X GET "https:///get_data_for_encryption?address=0xb9C303443c9af84777e60D5C987AbF0c43844918&identityPrefix=0x79bc8f6b4fcb02c651d6a702b7ad965c7fca19e94a9646d21ae90c8b54c030a0&triggerDefinition=0x01f86694953a0425accee2e05f22e78999c595ed2ee7183cf84fe480e205a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efe401e205a0000000000000000000000000812a6755975485c6e340f97de6790b34a94d1430c404c20402" +curl -X GET "https:///event/get_data_for_encryption?identityPrefix=0x79bc8f6b4fcb02c651d6a702b7ad965c7fca19e94a9646d21ae90c8b54c030a0&triggerDefinition=0x01f86694953a0425accee2e05f22e78999c595ed2ee7183cf84fe480e205a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efe401e205a0000000000000000000000000812a6755975485c6e340f97de6790b34a94d1430c404c20402" ``` #### Example Response @@ -369,16 +372,16 @@ console.log("Encrypted Commitment:", encryptedCommitment); #### 3.A Retrieve the Decryption Key -After the decryption trigger conditions are met (i.e., the specified timestamp has passed), retrieve the decryption key using the `/get_decryption_key` endpoint, or for event based decryption triggers the `get_event_decryption_key` endpoint. +After the decryption trigger conditions are met (i.e., the specified timestamp has passed), retrieve the decryption key using the `/time/get_decryption_key` endpoint. or for event based decryption triggers the `/event/get_decryption_key` endpoint. Refer to the Swagger documentation for detailed usage. -#### Example Request +#### Example Request (Time-Based) ```bash -curl -X GET "https:///get_decryption_key?identity=0x8c232eae4f957259e9d6b68301d529e9851b8642874c8f59d2bd0fb84a570c75" +curl -X GET "https:///time/get_decryption_key?identity=0x8c232eae4f957259e9d6b68301d529e9851b8642874c8f59d2bd0fb84a570c75" ``` -#### Example Response +#### Example Response (Time-Based) ```json { "decryption_key": "0x99a805fc26812c13041126b25e91eccf3de464d1df7a95d1edca8831a9ec02dd", @@ -387,6 +390,21 @@ curl -X GET "https:///get_decryption_key?identity=0x8c232eae4f9572 } ``` +#### Example Request (Event-Based) +```bash +curl -X GET "https:///event/get_decryption_key?identity=0x8c232eae4f957259e9d6b68301d529e9851b8642874c8f59d2bd0fb84a570c75&eon=1" +``` +Note: If eon is not passed, the api will fetch the current eon + +#### Example Response (Event-Based) +```json +{ + "decryption_key": "0x99a805fc26812c13041126b25e91eccf3de464d1df7a95d1edca8831a9ec02dd", + "decryption_timestamp": 0, + "identity": "0x8c232eae4f957259e9d6b68301d529e9851b8642874c8f59d2bd0fb84a570c75" +} +``` + #### 3.B Decrypt Commitments Once you have the decryption key, use it to decrypt commitments encrypted with the Shutter system. The `/decrypt_commitment` endpoint enables this process. diff --git a/docker-compose.rate_limit.yaml b/docker-compose.rate_limit.yaml index b5910fe..821410a 100644 --- a/docker-compose.rate_limit.yaml +++ b/docker-compose.rate_limit.yaml @@ -30,11 +30,49 @@ services: # /check_authentication endpoint caddy.@checkauth.path_0: "/check_authentication" caddy.@checkauth.path_1: "/api/check_authentication" - caddy.handle: "@checkauth" - caddy.handle.handle_0: "@withApiKey" - caddy.handle.handle_0.respond: "`{\"result\": \"authenticated\"}`" - caddy.handle.handle_1: "@noApiKey" - caddy.handle.handle_1.respond: "`{\"result\": \"unauthenticated\"}`" + caddy.handle_7: "@checkauth" + caddy.handle_7.handle_0: "@withApiKey" + caddy.handle_7.handle_0.respond: "`{\"result\": \"authenticated\"}`" + caddy.handle_7.handle_1: "@noApiKey" + caddy.handle_7.handle_1.respond: "`{\"result\": \"unauthenticated\"}`" + + ## Redirects from old endpoints to new ones (308 Permanent Redirect - preserves HTTP method and request body): + # /api/register_event_identity -> /api/event/register_identity + caddy.@redirect_register_event_identity.path: "/api/register_event_identity*" + caddy.@redirect_register_event_identity.method: POST + caddy.redir_0: "@redirect_register_event_identity /api/event/register_identity 308" + + # /api/register_identity -> /api/time/register_identity + caddy.@redirect_register_identity.path: "/api/register_identity*" + caddy.@redirect_register_identity.method: POST + caddy.redir_1: "@redirect_register_identity /api/time/register_identity 308" + + # /api/get_data_for_encryption -> /api/time/get_data_for_encryption + # Note: This redirects to time-based by default. + caddy.@redirect_get_data_for_encryption.path: "/api/get_data_for_encryption*" + caddy.@redirect_get_data_for_encryption.method: GET + caddy.redir_2: "@redirect_get_data_for_encryption /api/time/get_data_for_encryption?{query} 308" + + # /api/compile_event_trigger_definition -> /api/event/compile_trigger_definition + caddy.@redirect_compile_event_trigger_definition.path: "/api/compile_event_trigger_definition*" + caddy.@redirect_compile_event_trigger_definition.method: POST + caddy.redir_3: "@redirect_compile_event_trigger_definition /api/event/compile_trigger_definition 308" + + # /api/get_decryption_key -> /api/time/get_decryption_key + caddy.@redirect_get_decryption_key.path: "/api/get_decryption_key*" + caddy.@redirect_get_decryption_key.method: GET + caddy.redir_4: "@redirect_get_decryption_key /api/time/get_decryption_key?{query} 308" + + # /api/get_event_trigger_expiration_block -> /api/event/get_trigger_expiration_block + caddy.@redirect_get_event_trigger_expiration_block.path: "/api/get_event_trigger_expiration_block*" + caddy.@redirect_get_event_trigger_expiration_block.method: GET + caddy.redir_5: "@redirect_get_event_trigger_expiration_block /api/event/get_trigger_expiration_block?{query} 308" + + # /api/get_event_decryption_key -> /api/event/get_decryption_key + caddy.@redirect_get_event_decryption_key.path: "/api/get_event_decryption_key*" + caddy.@redirect_get_event_decryption_key.method: GET + caddy.redir_6: "@redirect_get_event_decryption_key /api/event/get_decryption_key?{query} 308" + ## Rate limiting: # Make sure to mount compiled 'apikeys' file to this path in caddy container: caddy.import: /etc/caddy/apikeys @@ -50,21 +88,21 @@ services: caddy.rate_limit_0.zone_0.key: "{remote_host}" caddy.rate_limit_0.zone_0.events: 5 caddy.rate_limit_0.zone_0.window: 1d - caddy.rate_limit_0.zone_0.match.path: "*/register_identity*" + caddy.rate_limit_0.zone_0.match.path: "*/time/register_identity*" caddy.rate_limit_0.zone_0.match.method: POST caddy.rate_limit_0.zone_1: get_data_for_encryption__unauthorized caddy.rate_limit_0.zone_1.key: "{remote_host}" caddy.rate_limit_0.zone_1.events: 10 caddy.rate_limit_0.zone_1.window: 1d - caddy.rate_limit_0.zone_1.match.path: "*/get_data_for_encryption*" + caddy.rate_limit_0.zone_1.match.path: "*/time/get_data_for_encryption*" caddy.rate_limit_0.zone_1.match.method: GET caddy.rate_limit_0.zone_2: get_decryption_key__unauthorized caddy.rate_limit_0.zone_2.key: "{remote_host}" caddy.rate_limit_0.zone_2.events: 20 caddy.rate_limit_0.zone_2.window: 1d - caddy.rate_limit_0.zone_2.match.path: "*/get_decryption_key*" + caddy.rate_limit_0.zone_2.match.path: "*/time/get_decryption_key*" caddy.rate_limit_0.zone_2.match.method: GET caddy.rate_limit_0.zone_3: decrypt_commitment__unauthorized @@ -78,30 +116,37 @@ services: caddy.rate_limit_0.zone_4.key: "{remote_host}" caddy.rate_limit_0.zone_4.events: 20 caddy.rate_limit_0.zone_4.window: 1d - caddy.rate_limit_0.zone_4.match.path: "*/compile_event_trigger_definition*" + caddy.rate_limit_0.zone_4.match.path: "*/event/compile_trigger_definition*" caddy.rate_limit_0.zone_4.match.method: POST caddy.rate_limit_0.zone_5: register_event_identity__unauthorized caddy.rate_limit_0.zone_5.key: "{remote_host}" caddy.rate_limit_0.zone_5.events: 5 caddy.rate_limit_0.zone_5.window: 1d - caddy.rate_limit_0.zone_5.match.path: "*/register_event_identity*" + caddy.rate_limit_0.zone_5.match.path: "*/event/register_identity*" caddy.rate_limit_0.zone_5.match.method: POST caddy.rate_limit_0.zone_6: get_event_trigger_expiration_block__unauthorized caddy.rate_limit_0.zone_6.key: "{remote_host}" caddy.rate_limit_0.zone_6.events: 20 caddy.rate_limit_0.zone_6.window: 1d - caddy.rate_limit_0.zone_6.match.path: "*/get_event_trigger_expiration_block*" + caddy.rate_limit_0.zone_6.match.path: "*/event/get_trigger_expiration_block*" caddy.rate_limit_0.zone_6.match.method: GET caddy.rate_limit_0.zone_7: get_event_decryption_key__unauthorized caddy.rate_limit_0.zone_7.key: "{remote_host}" caddy.rate_limit_0.zone_7.events: 20 caddy.rate_limit_0.zone_7.window: 1d - caddy.rate_limit_0.zone_7.match.path: "*/get_event_decryption_key*" + caddy.rate_limit_0.zone_7.match.path: "*/event/get_decryption_key*" caddy.rate_limit_0.zone_7.match.method: GET + caddy.rate_limit_0.zone_8: event_get_data_for_encryption__unauthorized + caddy.rate_limit_0.zone_8.key: "{remote_host}" + caddy.rate_limit_0.zone_8.events: 10 + caddy.rate_limit_0.zone_8.window: 1d + caddy.rate_limit_0.zone_8.match.path: "*/event/get_data_for_encryption*" + caddy.rate_limit_0.zone_8.match.method: GET + # Rate limits with api key caddy.rate_limit_1: "@withApiKey" caddy.rate_limit_1.log_key: " " @@ -110,21 +155,21 @@ services: caddy.rate_limit_1.zone_0.key: "{header.Authorization}" caddy.rate_limit_1.zone_0.events: 500 caddy.rate_limit_1.zone_0.window: 1d - caddy.rate_limit_1.zone_0.match.path: "*/register_identity*" + caddy.rate_limit_1.zone_0.match.path: "*/time/register_identity*" caddy.rate_limit_1.zone_0.match.method: POST caddy.rate_limit_1.zone_1: get_data_for_encryption__authorized caddy.rate_limit_1.zone_1.key: "{header.Authorization}" caddy.rate_limit_1.zone_1.events: 1000 caddy.rate_limit_1.zone_1.window: 1d - caddy.rate_limit_1.zone_1.match.path: "*/get_data_for_encryption*" + caddy.rate_limit_1.zone_1.match.path: "*/time/get_data_for_encryption*" caddy.rate_limit_1.zone_1.match.method: GET caddy.rate_limit_1.zone_2: get_decryption_key__authorized caddy.rate_limit_1.zone_2.key: "{header.Authorization}" caddy.rate_limit_1.zone_2.events: 2000 caddy.rate_limit_1.zone_2.window: 1d - caddy.rate_limit_1.zone_2.match.path: "*/get_decryption_key*" + caddy.rate_limit_1.zone_2.match.path: "*/time/get_decryption_key*" caddy.rate_limit_1.zone_2.match.method: GET caddy.rate_limit_1.zone_3: decrypt_commitment__authorized @@ -138,30 +183,36 @@ services: caddy.rate_limit_1.zone_4.key: "{header.Authorization}" caddy.rate_limit_1.zone_4.events: 2000 caddy.rate_limit_1.zone_4.window: 1d - caddy.rate_limit_1.zone_4.match.path: "*/compile_event_trigger_definition*" + caddy.rate_limit_1.zone_4.match.path: "*/event/compile_trigger_definition*" caddy.rate_limit_1.zone_4.match.method: POST caddy.rate_limit_1.zone_5: register_event_identity__authorized caddy.rate_limit_1.zone_5.key: "{header.Authorization}" caddy.rate_limit_1.zone_5.events: 500 caddy.rate_limit_1.zone_5.window: 1d - caddy.rate_limit_1.zone_5.match.path: "*/register_event_identity*" + caddy.rate_limit_1.zone_5.match.path: "*/event/register_identity*" caddy.rate_limit_1.zone_5.match.method: POST caddy.rate_limit_1.zone_6: get_event_trigger_expiration_block__authorized caddy.rate_limit_1.zone_6.key: "{header.Authorization}" caddy.rate_limit_1.zone_6.events: 2000 caddy.rate_limit_1.zone_6.window: 1d - caddy.rate_limit_1.zone_6.match.path: "*/get_event_trigger_expiration_block*" + caddy.rate_limit_1.zone_6.match.path: "*/event/get_trigger_expiration_block*" caddy.rate_limit_1.zone_6.match.method: GET caddy.rate_limit_1.zone_7: get_event_decryption_key__authorized caddy.rate_limit_1.zone_7.key: "{header.Authorization}" caddy.rate_limit_1.zone_7.events: 2000 caddy.rate_limit_1.zone_7.window: 1d - caddy.rate_limit_1.zone_7.match.path: "*/get_event_decryption_key*" + caddy.rate_limit_1.zone_7.match.path: "*/event/get_decryption_key*" caddy.rate_limit_1.zone_7.match.method: GET + caddy.rate_limit_1.zone_8: event_get_data_for_encryption__authorized + caddy.rate_limit_1.zone_8.key: "{header.Authorization}" + caddy.rate_limit_1.zone_8.events: 1000 + caddy.rate_limit_1.zone_8.window: 1d + caddy.rate_limit_1.zone_8.match.path: "*/event/get_data_for_encryption*" + caddy.rate_limit_1.zone_8.match.method: GET caddy: build: diff --git a/docs/docs.go b/docs/docs.go index d07b27b..1a5602f 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -15,60 +15,6 @@ const docTemplate = `{ "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { - "/compile_event_trigger_definition": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "This endpoint takes an event signature snippet and some arguments to create an event trigger definition that will be understood by keypers", - "produces": [ - "application/json" - ], - "tags": [ - "Crypto" - ], - "summary": "Allows clients to compile an event trigger definition string.", - "parameters": [ - { - "description": "Event signature and match arguments.", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/EventTriggerDefinitionRequest" - } - } - ], - "responses": { - "200": { - "description": "Success.", - "schema": { - "$ref": "#/definitions/usecase.EventTriggerDefinitionResponse" - } - }, - "400": { - "description": "Invalid Event Data.", - "schema": { - "$ref": "#/definitions/error.Http" - } - }, - "429": { - "description": "Too many requests. Rate limited.", - "schema": { - "$ref": "#/definitions/error.Http" - } - }, - "500": { - "description": "Internal server error.", - "schema": { - "$ref": "#/definitions/error.Http" - } - } - } - } - }, "/decrypt_commitment": { "get": { "security": [ @@ -139,51 +85,41 @@ const docTemplate = `{ } } }, - "/get_data_for_encryption": { - "get": { + "/event/compile_trigger_definition": { + "post": { "security": [ { "BearerAuth": [] } ], - "description": "Retrieves all the necessary data required by clients for encrypting any message. Supports both time-based and event-based identity computation. If triggerDefinition is provided, the identity will be computed for event-based triggers. Otherwise, it uses time-based identity computation.", + "description": "This endpoint takes an event signature snippet and some arguments to create an event trigger definition that will be understood by keypers", "produces": [ "application/json" ], "tags": [ "Crypto" ], - "summary": "Provides data necessary to allow encryption.", + "summary": "Allows clients to compile an event trigger definition string.", "parameters": [ { - "type": "string", - "description": "Ethereum address associated with the identity. Time‑based: use the address that will register the identity (your account if self‑registering, or the API signer address below if you are using the API register endpoint). Event‑based (triggerDefinition provided): users cannot self‑register because the registry is owner‑only, please use the API signer address below. Gnosis Mainnet API address: 0x228DefCF37Da29475F0EE2B9E4dfAeDc3b0746bc Chiado API address: 0xb9C303443c9af84777e60D5C987AbF0c43844918", - "name": "address", - "in": "query", - "required": true - }, - { - "type": "string", - "description": "Optional identity prefix. You can generate it on your end and pass it to this endpoint, or allow the API to randomly generate one for you.", - "name": "identityPrefix", - "in": "query" - }, - { - "type": "string", - "description": "Optional event trigger definition (hex-encoded with 0x prefix). If provided, identity will be computed for event-based triggers. This parameter is strictly for event-based triggers.", - "name": "triggerDefinition", - "in": "query" + "description": "Event signature and match arguments.", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/EventTriggerDefinitionRequest" + } } ], "responses": { "200": { "description": "Success.", "schema": { - "$ref": "#/definitions/GetDataForEncryption" + "$ref": "#/definitions/usecase.EventTriggerDefinitionResponse" } }, "400": { - "description": "Invalid Get data for encryption request.", + "description": "Invalid Event Data.", "schema": { "$ref": "#/definitions/error.Http" } @@ -203,45 +139,45 @@ const docTemplate = `{ } } }, - "/get_decryption_key": { + "/event/get_data_for_encryption": { "get": { "security": [ { "BearerAuth": [] } ], - "description": "Retrieves a decryption key for a given registered identity once the timestamp is reached. Decryption key is 0x padded, clients need to remove the prefix when decrypting on their end.", + "description": "Retrieves all the necessary data required by clients for encrypting any message using event-based identity computation.", "produces": [ "application/json" ], "tags": [ "Crypto" ], - "summary": "Get decryption key.", + "summary": "Provides data necessary to allow event-based encryption.", "parameters": [ { "type": "string", - "description": "Identity associated with the decryption key.", - "name": "identity", + "description": "Event trigger definition (hex-encoded with 0x prefix).", + "name": "triggerDefinition", "in": "query", "required": true + }, + { + "type": "string", + "description": "Optional identity prefix. You can generate it on your end and pass it to this endpoint, or allow the API to randomly generate one for you.", + "name": "identityPrefix", + "in": "query" } ], "responses": { "200": { "description": "Success.", "schema": { - "$ref": "#/definitions/GetDecryptionKey" + "$ref": "#/definitions/GetDataForEncryption" } }, "400": { - "description": "Invalid Get decryption key request.", - "schema": { - "$ref": "#/definitions/error.Http" - } - }, - "404": { - "description": "Decryption key not found for the associated identity.", + "description": "Invalid Get data for encryption request.", "schema": { "$ref": "#/definitions/error.Http" } @@ -261,7 +197,7 @@ const docTemplate = `{ } } }, - "/get_event_decryption_key": { + "/event/get_decryption_key": { "get": { "security": [ { @@ -326,7 +262,7 @@ const docTemplate = `{ } } }, - "/get_event_trigger_expiration_block": { + "/event/get_trigger_expiration_block": { "get": { "security": [ { @@ -392,7 +328,7 @@ const docTemplate = `{ } } }, - "/register_event_identity": { + "/event/register_identity": { "post": { "security": [ { @@ -446,7 +382,123 @@ const docTemplate = `{ } } }, - "/register_identity": { + "/time/get_data_for_encryption": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Retrieves all the necessary data required by clients for encrypting any message using time-based identity computation.", + "produces": [ + "application/json" + ], + "tags": [ + "Crypto" + ], + "summary": "Provides data necessary to allow time-based encryption.", + "parameters": [ + { + "type": "string", + "description": "Ethereum address associated with the identity. If you are registering the identity yourself, pass the address of the account making the registration. If you want the API to register the identity on gnosis mainnet, pass the address: 0x228DefCF37Da29475F0EE2B9E4dfAeDc3b0746bc. For chiado pass the address: 0xb9C303443c9af84777e60D5C987AbF0c43844918", + "name": "address", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "Optional identity prefix. You can generate it on your end and pass it to this endpoint, or allow the API to randomly generate one for you.", + "name": "identityPrefix", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success.", + "schema": { + "$ref": "#/definitions/GetDataForEncryption" + } + }, + "400": { + "description": "Invalid Get data for encryption request.", + "schema": { + "$ref": "#/definitions/error.Http" + } + }, + "429": { + "description": "Too many requests. Rate limited.", + "schema": { + "$ref": "#/definitions/error.Http" + } + }, + "500": { + "description": "Internal server error.", + "schema": { + "$ref": "#/definitions/error.Http" + } + } + } + } + }, + "/time/get_decryption_key": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Retrieves a decryption key for a given registered identity once the timestamp is reached. Decryption key is 0x padded, clients need to remove the prefix when decrypting on their end.", + "produces": [ + "application/json" + ], + "tags": [ + "Crypto" + ], + "summary": "Get decryption key.", + "parameters": [ + { + "type": "string", + "description": "Identity associated with the decryption key.", + "name": "identity", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "Success.", + "schema": { + "$ref": "#/definitions/GetDecryptionKey" + } + }, + "400": { + "description": "Invalid Get decryption key request.", + "schema": { + "$ref": "#/definitions/error.Http" + } + }, + "404": { + "description": "Decryption key not found for the associated identity.", + "schema": { + "$ref": "#/definitions/error.Http" + } + }, + "429": { + "description": "Too many requests. Rate limited.", + "schema": { + "$ref": "#/definitions/error.Http" + } + }, + "500": { + "description": "Internal server error.", + "schema": { + "$ref": "#/definitions/error.Http" + } + } + } + } + }, + "/time/register_identity": { "post": { "security": [ { diff --git a/docs/swagger.json b/docs/swagger.json index 71cb7b3..064debc 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -6,60 +6,6 @@ "contact": {} }, "paths": { - "/compile_event_trigger_definition": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "This endpoint takes an event signature snippet and some arguments to create an event trigger definition that will be understood by keypers", - "produces": [ - "application/json" - ], - "tags": [ - "Crypto" - ], - "summary": "Allows clients to compile an event trigger definition string.", - "parameters": [ - { - "description": "Event signature and match arguments.", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/EventTriggerDefinitionRequest" - } - } - ], - "responses": { - "200": { - "description": "Success.", - "schema": { - "$ref": "#/definitions/usecase.EventTriggerDefinitionResponse" - } - }, - "400": { - "description": "Invalid Event Data.", - "schema": { - "$ref": "#/definitions/error.Http" - } - }, - "429": { - "description": "Too many requests. Rate limited.", - "schema": { - "$ref": "#/definitions/error.Http" - } - }, - "500": { - "description": "Internal server error.", - "schema": { - "$ref": "#/definitions/error.Http" - } - } - } - } - }, "/decrypt_commitment": { "get": { "security": [ @@ -130,51 +76,41 @@ } } }, - "/get_data_for_encryption": { - "get": { + "/event/compile_trigger_definition": { + "post": { "security": [ { "BearerAuth": [] } ], - "description": "Retrieves all the necessary data required by clients for encrypting any message. Supports both time-based and event-based identity computation. If triggerDefinition is provided, the identity will be computed for event-based triggers. Otherwise, it uses time-based identity computation.", + "description": "This endpoint takes an event signature snippet and some arguments to create an event trigger definition that will be understood by keypers", "produces": [ "application/json" ], "tags": [ "Crypto" ], - "summary": "Provides data necessary to allow encryption.", + "summary": "Allows clients to compile an event trigger definition string.", "parameters": [ { - "type": "string", - "description": "Ethereum address associated with the identity. Time‑based: use the address that will register the identity (your account if self‑registering, or the API signer address below if you are using the API register endpoint). Event‑based (triggerDefinition provided): users cannot self‑register because the registry is owner‑only, please use the API signer address below. Gnosis Mainnet API address: 0x228DefCF37Da29475F0EE2B9E4dfAeDc3b0746bc Chiado API address: 0xb9C303443c9af84777e60D5C987AbF0c43844918", - "name": "address", - "in": "query", - "required": true - }, - { - "type": "string", - "description": "Optional identity prefix. You can generate it on your end and pass it to this endpoint, or allow the API to randomly generate one for you.", - "name": "identityPrefix", - "in": "query" - }, - { - "type": "string", - "description": "Optional event trigger definition (hex-encoded with 0x prefix). If provided, identity will be computed for event-based triggers. This parameter is strictly for event-based triggers.", - "name": "triggerDefinition", - "in": "query" + "description": "Event signature and match arguments.", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/EventTriggerDefinitionRequest" + } } ], "responses": { "200": { "description": "Success.", "schema": { - "$ref": "#/definitions/GetDataForEncryption" + "$ref": "#/definitions/usecase.EventTriggerDefinitionResponse" } }, "400": { - "description": "Invalid Get data for encryption request.", + "description": "Invalid Event Data.", "schema": { "$ref": "#/definitions/error.Http" } @@ -194,45 +130,45 @@ } } }, - "/get_decryption_key": { + "/event/get_data_for_encryption": { "get": { "security": [ { "BearerAuth": [] } ], - "description": "Retrieves a decryption key for a given registered identity once the timestamp is reached. Decryption key is 0x padded, clients need to remove the prefix when decrypting on their end.", + "description": "Retrieves all the necessary data required by clients for encrypting any message using event-based identity computation.", "produces": [ "application/json" ], "tags": [ "Crypto" ], - "summary": "Get decryption key.", + "summary": "Provides data necessary to allow event-based encryption.", "parameters": [ { "type": "string", - "description": "Identity associated with the decryption key.", - "name": "identity", + "description": "Event trigger definition (hex-encoded with 0x prefix).", + "name": "triggerDefinition", "in": "query", "required": true + }, + { + "type": "string", + "description": "Optional identity prefix. You can generate it on your end and pass it to this endpoint, or allow the API to randomly generate one for you.", + "name": "identityPrefix", + "in": "query" } ], "responses": { "200": { "description": "Success.", "schema": { - "$ref": "#/definitions/GetDecryptionKey" + "$ref": "#/definitions/GetDataForEncryption" } }, "400": { - "description": "Invalid Get decryption key request.", - "schema": { - "$ref": "#/definitions/error.Http" - } - }, - "404": { - "description": "Decryption key not found for the associated identity.", + "description": "Invalid Get data for encryption request.", "schema": { "$ref": "#/definitions/error.Http" } @@ -252,7 +188,7 @@ } } }, - "/get_event_decryption_key": { + "/event/get_decryption_key": { "get": { "security": [ { @@ -317,7 +253,7 @@ } } }, - "/get_event_trigger_expiration_block": { + "/event/get_trigger_expiration_block": { "get": { "security": [ { @@ -383,7 +319,7 @@ } } }, - "/register_event_identity": { + "/event/register_identity": { "post": { "security": [ { @@ -437,7 +373,123 @@ } } }, - "/register_identity": { + "/time/get_data_for_encryption": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Retrieves all the necessary data required by clients for encrypting any message using time-based identity computation.", + "produces": [ + "application/json" + ], + "tags": [ + "Crypto" + ], + "summary": "Provides data necessary to allow time-based encryption.", + "parameters": [ + { + "type": "string", + "description": "Ethereum address associated with the identity. If you are registering the identity yourself, pass the address of the account making the registration. If you want the API to register the identity on gnosis mainnet, pass the address: 0x228DefCF37Da29475F0EE2B9E4dfAeDc3b0746bc. For chiado pass the address: 0xb9C303443c9af84777e60D5C987AbF0c43844918", + "name": "address", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "Optional identity prefix. You can generate it on your end and pass it to this endpoint, or allow the API to randomly generate one for you.", + "name": "identityPrefix", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success.", + "schema": { + "$ref": "#/definitions/GetDataForEncryption" + } + }, + "400": { + "description": "Invalid Get data for encryption request.", + "schema": { + "$ref": "#/definitions/error.Http" + } + }, + "429": { + "description": "Too many requests. Rate limited.", + "schema": { + "$ref": "#/definitions/error.Http" + } + }, + "500": { + "description": "Internal server error.", + "schema": { + "$ref": "#/definitions/error.Http" + } + } + } + } + }, + "/time/get_decryption_key": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Retrieves a decryption key for a given registered identity once the timestamp is reached. Decryption key is 0x padded, clients need to remove the prefix when decrypting on their end.", + "produces": [ + "application/json" + ], + "tags": [ + "Crypto" + ], + "summary": "Get decryption key.", + "parameters": [ + { + "type": "string", + "description": "Identity associated with the decryption key.", + "name": "identity", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "Success.", + "schema": { + "$ref": "#/definitions/GetDecryptionKey" + } + }, + "400": { + "description": "Invalid Get decryption key request.", + "schema": { + "$ref": "#/definitions/error.Http" + } + }, + "404": { + "description": "Decryption key not found for the associated identity.", + "schema": { + "$ref": "#/definitions/error.Http" + } + }, + "429": { + "description": "Too many requests. Rate limited.", + "schema": { + "$ref": "#/definitions/error.Http" + } + }, + "500": { + "description": "Internal server error.", + "schema": { + "$ref": "#/definitions/error.Http" + } + } + } + } + }, + "/time/register_identity": { "post": { "security": [ { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 1664043..ddca31a 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -126,41 +126,6 @@ info: release the decryption keys. title: Shutter API paths: - /compile_event_trigger_definition: - post: - description: This endpoint takes an event signature snippet and some arguments - to create an event trigger definition that will be understood by keypers - parameters: - - description: Event signature and match arguments. - in: body - name: request - required: true - schema: - $ref: '#/definitions/EventTriggerDefinitionRequest' - produces: - - application/json - responses: - "200": - description: Success. - schema: - $ref: '#/definitions/usecase.EventTriggerDefinitionResponse' - "400": - description: Invalid Event Data. - schema: - $ref: '#/definitions/error.Http' - "429": - description: Too many requests. Rate limited. - schema: - $ref: '#/definitions/error.Http' - "500": - description: Internal server error. - schema: - $ref: '#/definitions/error.Http' - security: - - BearerAuth: [] - summary: Allows clients to compile an event trigger definition string. - tags: - - Crypto /decrypt_commitment: get: description: Provides a way for clients to easily decrypt their encrypted message @@ -210,45 +175,26 @@ paths: summary: Allows clients to decrypt their encrypted message. tags: - Crypto - /get_data_for_encryption: - get: - description: Retrieves all the necessary data required by clients for encrypting - any message. Supports both time-based and event-based identity computation. - If triggerDefinition is provided, the identity will be computed for event-based - triggers. Otherwise, it uses time-based identity computation. + /event/compile_trigger_definition: + post: + description: This endpoint takes an event signature snippet and some arguments + to create an event trigger definition that will be understood by keypers parameters: - - description: 'Ethereum address associated with the identity. Time‑based: use - the address that will register the identity (your account if self‑registering, - or the API signer address below if you are using the API register endpoint). - Event‑based (triggerDefinition provided): users cannot self‑register because - the registry is owner‑only, please use the API signer address below. Gnosis - Mainnet API address: 0x228DefCF37Da29475F0EE2B9E4dfAeDc3b0746bc Chiado API - address: 0xb9C303443c9af84777e60D5C987AbF0c43844918' - in: query - name: address + - description: Event signature and match arguments. + in: body + name: request required: true - type: string - - description: Optional identity prefix. You can generate it on your end and - pass it to this endpoint, or allow the API to randomly generate one for - you. - in: query - name: identityPrefix - type: string - - description: Optional event trigger definition (hex-encoded with 0x prefix). - If provided, identity will be computed for event-based triggers. This parameter - is strictly for event-based triggers. - in: query - name: triggerDefinition - type: string + schema: + $ref: '#/definitions/EventTriggerDefinitionRequest' produces: - application/json responses: "200": description: Success. schema: - $ref: '#/definitions/GetDataForEncryption' + $ref: '#/definitions/usecase.EventTriggerDefinitionResponse' "400": - description: Invalid Get data for encryption request. + description: Invalid Event Data. schema: $ref: '#/definitions/error.Http' "429": @@ -261,33 +207,34 @@ paths: $ref: '#/definitions/error.Http' security: - BearerAuth: [] - summary: Provides data necessary to allow encryption. + summary: Allows clients to compile an event trigger definition string. tags: - Crypto - /get_decryption_key: + /event/get_data_for_encryption: get: - description: Retrieves a decryption key for a given registered identity once - the timestamp is reached. Decryption key is 0x padded, clients need to remove - the prefix when decrypting on their end. + description: Retrieves all the necessary data required by clients for encrypting + any message using event-based identity computation. parameters: - - description: Identity associated with the decryption key. + - description: Event trigger definition (hex-encoded with 0x prefix). in: query - name: identity + name: triggerDefinition required: true type: string + - description: Optional identity prefix. You can generate it on your end and + pass it to this endpoint, or allow the API to randomly generate one for + you. + in: query + name: identityPrefix + type: string produces: - application/json responses: "200": description: Success. schema: - $ref: '#/definitions/GetDecryptionKey' + $ref: '#/definitions/GetDataForEncryption' "400": - description: Invalid Get decryption key request. - schema: - $ref: '#/definitions/error.Http' - "404": - description: Decryption key not found for the associated identity. + description: Invalid Get data for encryption request. schema: $ref: '#/definitions/error.Http' "429": @@ -300,10 +247,10 @@ paths: $ref: '#/definitions/error.Http' security: - BearerAuth: [] - summary: Get decryption key. + summary: Provides data necessary to allow event-based encryption. tags: - Crypto - /get_event_decryption_key: + /event/get_decryption_key: get: description: Retrieves a decryption key for a given registered event based identity once it was triggered. Decryption key is 0x padded, clients need to remove @@ -347,7 +294,7 @@ paths: summary: Get decryption key. tags: - Crypto - /get_event_trigger_expiration_block: + /event/get_trigger_expiration_block: get: description: Retrieves the expiration block number for a given event identity registration. @@ -392,7 +339,7 @@ paths: summary: Get event identity registration expiration block number. tags: - Crypto - /register_event_identity: + /event/register_identity: post: description: Allows clients to register an identity used for encryption and event trigger definition for the decryption key associated with the encrypted @@ -429,7 +376,90 @@ paths: summary: Allows clients to register an event trigger identity. tags: - Crypto - /register_identity: + /time/get_data_for_encryption: + get: + description: Retrieves all the necessary data required by clients for encrypting + any message using time-based identity computation. + parameters: + - description: 'Ethereum address associated with the identity. If you are registering + the identity yourself, pass the address of the account making the registration. + If you want the API to register the identity on gnosis mainnet, pass the + address: 0x228DefCF37Da29475F0EE2B9E4dfAeDc3b0746bc. For chiado pass the + address: 0xb9C303443c9af84777e60D5C987AbF0c43844918' + in: query + name: address + required: true + type: string + - description: Optional identity prefix. You can generate it on your end and + pass it to this endpoint, or allow the API to randomly generate one for + you. + in: query + name: identityPrefix + type: string + produces: + - application/json + responses: + "200": + description: Success. + schema: + $ref: '#/definitions/GetDataForEncryption' + "400": + description: Invalid Get data for encryption request. + schema: + $ref: '#/definitions/error.Http' + "429": + description: Too many requests. Rate limited. + schema: + $ref: '#/definitions/error.Http' + "500": + description: Internal server error. + schema: + $ref: '#/definitions/error.Http' + security: + - BearerAuth: [] + summary: Provides data necessary to allow time-based encryption. + tags: + - Crypto + /time/get_decryption_key: + get: + description: Retrieves a decryption key for a given registered identity once + the timestamp is reached. Decryption key is 0x padded, clients need to remove + the prefix when decrypting on their end. + parameters: + - description: Identity associated with the decryption key. + in: query + name: identity + required: true + type: string + produces: + - application/json + responses: + "200": + description: Success. + schema: + $ref: '#/definitions/GetDecryptionKey' + "400": + description: Invalid Get decryption key request. + schema: + $ref: '#/definitions/error.Http' + "404": + description: Decryption key not found for the associated identity. + schema: + $ref: '#/definitions/error.Http' + "429": + description: Too many requests. Rate limited. + schema: + $ref: '#/definitions/error.Http' + "500": + description: Internal server error. + schema: + $ref: '#/definitions/error.Http' + security: + - BearerAuth: [] + summary: Get decryption key. + tags: + - Crypto + /time/register_identity: post: description: Allows clients to register an identity used for encryption and specify a release timestamp for the decryption key associated with the encrypted diff --git a/internal/router/router.go b/internal/router/router.go index 6ce13f5..ae1fe6f 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -34,14 +34,23 @@ func NewRouter( docs.SwaggerInfo.BasePath = "/api" api := router.Group("/api") { - api.GET("/get_decryption_key", cryptoService.GetDecryptionKey) - api.GET("/get_event_decryption_key", cryptoService.GetEventDecryptionKey) - api.GET("/get_data_for_encryption", cryptoService.GetDataForEncryption) - api.POST("/register_identity", cryptoService.RegisterIdentity) - api.POST("/compile_event_trigger_definition", cryptoService.CompileEventTriggerDefinition) api.GET("/decrypt_commitment", cryptoService.DecryptCommitment) - api.POST("/register_event_identity", cryptoService.RegisterEventIdentity) - api.GET("/get_event_trigger_expiration_block", cryptoService.GetEventTriggerExpirationBlock) + + timeGroup := api.Group("/time") + { + timeGroup.POST("/register_identity", cryptoService.RegisterIdentity) + timeGroup.GET("/get_data_for_encryption", cryptoService.GetDataForEncryptionTime) + timeGroup.GET("/get_decryption_key", cryptoService.GetDecryptionKey) + } + + eventGroup := api.Group("/event") + { + eventGroup.POST("/compile_trigger_definition", cryptoService.CompileEventTriggerDefinition) + eventGroup.POST("/register_identity", cryptoService.RegisterEventIdentity) + eventGroup.GET("/get_data_for_encryption", cryptoService.GetDataForEncryptionEvent) + eventGroup.GET("/get_trigger_expiration_block", cryptoService.GetEventTriggerExpirationBlock) + eventGroup.GET("/get_decryption_key", cryptoService.GetEventDecryptionKey) + } } router.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, func(c *ginSwagger.Config) { c.Title = "Shutter-API" diff --git a/internal/service/crypto.go b/internal/service/crypto.go index 7afe453..2328dc4 100644 --- a/internal/service/crypto.go +++ b/internal/service/crypto.go @@ -55,7 +55,7 @@ func NewCryptoService( // @Failure 429 {object} error.Http "Too many requests. Rate limited." // @Failure 500 {object} error.Http "Internal server error." // @Security BearerAuth -// @Router /get_event_decryption_key [get] +// @Router /event/get_decryption_key [get] func (svc *CryptoService) GetEventDecryptionKey(ctx *gin.Context) { identity, ok := ctx.GetQuery("identity") if !ok { @@ -108,7 +108,7 @@ func (svc *CryptoService) GetEventDecryptionKey(ctx *gin.Context) { // @Failure 429 {object} error.Http "Too many requests. Rate limited." // @Failure 500 {object} error.Http "Internal server error." // @Security BearerAuth -// @Router /get_decryption_key [get] +// @Router /time/get_decryption_key [get] func (svc *CryptoService) GetDecryptionKey(ctx *gin.Context) { identity, ok := ctx.GetQuery("identity") if !ok { @@ -133,22 +133,21 @@ func (svc *CryptoService) GetDecryptionKey(ctx *gin.Context) { // @BasePath /api // -// GetDataForEncryption godoc +// GetDataForEncryptionTime godoc // -// @Summary Provides data necessary to allow encryption. -// @Description Retrieves all the necessary data required by clients for encrypting any message. Supports both time-based and event-based identity computation. If triggerDefinition is provided, the identity will be computed for event-based triggers. Otherwise, it uses time-based identity computation. +// @Summary Provides data necessary to allow time-based encryption. +// @Description Retrieves all the necessary data required by clients for encrypting any message using time-based identity computation. // @Tags Crypto // @Produce json -// @Param address query string true "Ethereum address associated with the identity. Time‑based: use the address that will register the identity (your account if self‑registering, or the API signer address below if you are using the API register endpoint). Event‑based (triggerDefinition provided): users cannot self‑register because the registry is owner‑only, please use the API signer address below. Gnosis Mainnet API address: 0x228DefCF37Da29475F0EE2B9E4dfAeDc3b0746bc Chiado API address: 0xb9C303443c9af84777e60D5C987AbF0c43844918" +// @Param address query string true "Ethereum address associated with the identity. If you are registering the identity yourself, pass the address of the account making the registration. If you want the API to register the identity on gnosis mainnet, pass the address: 0x228DefCF37Da29475F0EE2B9E4dfAeDc3b0746bc. For chiado pass the address: 0xb9C303443c9af84777e60D5C987AbF0c43844918" // @Param identityPrefix query string false "Optional identity prefix. You can generate it on your end and pass it to this endpoint, or allow the API to randomly generate one for you." -// @Param triggerDefinition query string false "Optional event trigger definition (hex-encoded with 0x prefix). If provided, identity will be computed for event-based triggers. This parameter is strictly for event-based triggers." // @Success 200 {object} usecase.GetDataForEncryptionResponse "Success." // @Failure 400 {object} error.Http "Invalid Get data for encryption request." // @Failure 429 {object} error.Http "Too many requests. Rate limited." // @Failure 500 {object} error.Http "Internal server error." // @Security BearerAuth -// @Router /get_data_for_encryption [get] -func (svc *CryptoService) GetDataForEncryption(ctx *gin.Context) { +// @Router /time/get_data_for_encryption [get] +func (svc *CryptoService) GetDataForEncryptionTime(ctx *gin.Context) { address, ok := ctx.GetQuery("address") if !ok { err := sherror.NewHttpError( @@ -165,12 +164,50 @@ func (svc *CryptoService) GetDataForEncryption(ctx *gin.Context) { identityPrefix = "" } - triggerDefinition, ok := ctx.GetQuery("triggerDefinition") + data, httpErr := svc.CryptoUsecase.GetDataForEncryption(ctx, address, identityPrefix, "") + if httpErr != nil { + ctx.Error(httpErr) + return + } + ctx.JSON(http.StatusOK, gin.H{ + "message": data, + }) +} + +// @BasePath /api +// +// GetDataForEncryptionEvent godoc +// +// @Summary Provides data necessary to allow event-based encryption. +// @Description Retrieves all the necessary data required by clients for encrypting any message using event-based identity computation. +// @Tags Crypto +// @Produce json +// @Param triggerDefinition query string true "Event trigger definition (hex-encoded with 0x prefix)." +// @Param identityPrefix query string false "Optional identity prefix. You can generate it on your end and pass it to this endpoint, or allow the API to randomly generate one for you." +// @Success 200 {object} usecase.GetDataForEncryptionResponse "Success." +// @Failure 400 {object} error.Http "Invalid Get data for encryption request." +// @Failure 429 {object} error.Http "Too many requests. Rate limited." +// @Failure 500 {object} error.Http "Internal server error." +// @Security BearerAuth +// @Router /event/get_data_for_encryption [get] +func (svc *CryptoService) GetDataForEncryptionEvent(ctx *gin.Context) { + identityPrefix, ok := ctx.GetQuery("identityPrefix") if !ok { - triggerDefinition = "" + identityPrefix = "" } - data, httpErr := svc.CryptoUsecase.GetDataForEncryption(ctx, address, identityPrefix, triggerDefinition) + triggerDefinition, ok := ctx.GetQuery("triggerDefinition") + if !ok || triggerDefinition == "" { + err := sherror.NewHttpError( + "query parameter not found", + "triggerDefinition query parameter is required for event-based get_data_for_encryption", + http.StatusBadRequest, + ) + ctx.Error(err) + return + } + + data, httpErr := svc.CryptoUsecase.GetDataForEncryptionEvent(ctx, identityPrefix, triggerDefinition) if httpErr != nil { ctx.Error(httpErr) return @@ -195,7 +232,7 @@ func (svc *CryptoService) GetDataForEncryption(ctx *gin.Context) { // @Failure 429 {object} error.Http "Too many requests. Rate limited." // @Failure 500 {object} error.Http "Internal server error." // @Security BearerAuth -// @Router /register_identity [post] +// @Router /time/register_identity [post] func (svc *CryptoService) RegisterIdentity(ctx *gin.Context) { var req RegisterIdentityRequest if err := ctx.ShouldBindJSON(&req); err != nil { @@ -260,10 +297,10 @@ func (svc *CryptoService) DecryptCommitment(ctx *gin.Context) { } eon := int64(-1) eonArg, ok := ctx.GetQuery("eon") - var err error + var parseErr error if ok { - eon, err = strconv.ParseInt(eonArg, 10, 64) - if err != nil { + eon, parseErr = strconv.ParseInt(eonArg, 10, 64) + if parseErr != nil { err := sherror.NewHttpError( "query parameter invalid", "eon query parameter could not be parsed", @@ -318,7 +355,7 @@ func (svc *CryptoService) DecryptCommitment(ctx *gin.Context) { // @Failure 429 {object} error.Http "Too many requests. Rate limited." // @Failure 500 {object} error.Http "Internal server error." // @Security BearerAuth -// @Router /compile_event_trigger_definition [post] +// @Router /event/compile_trigger_definition [post] func (svc *CryptoService) CompileEventTriggerDefinition(ctx *gin.Context) { CompileEventTriggerDefinition(ctx) } @@ -360,7 +397,7 @@ func CompileEventTriggerDefinition(ctx *gin.Context) { // @Failure 429 {object} error.Http "Too many requests. Rate limited." // @Failure 500 {object} error.Http "Internal server error." // @Security BearerAuth -// @Router /register_event_identity [post] +// @Router /event/register_identity [post] func (svc *CryptoService) RegisterEventIdentity(ctx *gin.Context) { var req RegisterEventIdentityRequest if err := ctx.ShouldBindJSON(&req); err != nil { @@ -400,7 +437,7 @@ func (svc *CryptoService) RegisterEventIdentity(ctx *gin.Context) { // @Failure 429 {object} error.Http "Too many requests. Rate limited." // @Failure 500 {object} error.Http "Internal server error." // @Security BearerAuth -// @Router /get_event_trigger_expiration_block [get] +// @Router /event/get_trigger_expiration_block [get] func (svc *CryptoService) GetEventTriggerExpirationBlock(ctx *gin.Context) { eonStr, ok := ctx.GetQuery("eon") if !ok { diff --git a/internal/usecase/crypto.go b/internal/usecase/crypto.go index d7bd155..12e7b4d 100644 --- a/internal/usecase/crypto.go +++ b/internal/usecase/crypto.go @@ -114,6 +114,34 @@ func NewCryptoUsecase( } } +// getSigner returns the signer for the API signer address. +func (uc *CryptoUsecase) getSigner(ctx context.Context) (*bind.TransactOpts, *httpError.Http) { + chainID, err := uc.ethClient.ChainID(ctx) + if err != nil { + log.Err(err).Msg("err encountered while querying chain id") + metrics.TotalFailedRPCCalls.Inc() + err := httpError.NewHttpError( + "error encountered while querying chain id", + "", + http.StatusInternalServerError, + ) + return nil, &err + } + + newSigner, err := bind.NewKeyedTransactorWithChainID(uc.config.SigningKey, chainID) + if err != nil { + log.Err(err).Msg("err encountered while creating signer") + err := httpError.NewHttpError( + "error encountered while creating signer", + "", + http.StatusInternalServerError, + ) + return nil, &err + } + + return newSigner, nil +} + func (uc *CryptoUsecase) GetDecryptionKey(ctx context.Context, identity string) (*GetDecryptionKeyResponse, *httpError.Http) { identityBytes, err := hex.DecodeString(strings.TrimPrefix(string(identity), "0x")) if err != nil { @@ -350,6 +378,16 @@ func (uc *CryptoUsecase) GetDataForEncryption(ctx context.Context, address strin }, nil } +// GetDataForEncryptionEvent is the event-based variant which uses the API signer address to compute the identity. +func (uc *CryptoUsecase) GetDataForEncryptionEvent(ctx context.Context, identityPrefixStringified string, triggerDefinitionHex string) (*GetDataForEncryptionResponse, *httpError.Http) { + newSigner, httpErr := uc.getSigner(ctx) + if httpErr != nil { + return nil, httpErr + } + + return uc.GetDataForEncryption(ctx, newSigner.From.Hex(), identityPrefixStringified, triggerDefinitionHex) +} + func (uc *CryptoUsecase) RegisterIdentity(ctx context.Context, decryptionTimestamp uint64, identityPrefixStringified string) (*RegisterIdentityResponse, *httpError.Http) { currentTimestamp := time.Now().Unix() if currentTimestamp > int64(decryptionTimestamp) { @@ -447,27 +485,9 @@ func (uc *CryptoUsecase) RegisterIdentity(ctx context.Context, decryptionTimesta return nil, &err } - chainId, err := uc.ethClient.ChainID(ctx) - if err != nil { - log.Err(err).Msg("err encountered while quering chain id") - metrics.TotalFailedRPCCalls.Inc() - err := httpError.NewHttpError( - "error encountered while querying chain id", - "", - http.StatusInternalServerError, - ) - return nil, &err - } - - newSigner, err := bind.NewKeyedTransactorWithChainID(uc.config.SigningKey, chainId) - if err != nil { - log.Err(err).Msg("err encountered while creating signer") - err := httpError.NewHttpError( - "error encountered while registering identity", - "", - http.StatusInternalServerError, - ) - return nil, &err + newSigner, httpErr := uc.getSigner(ctx) + if httpErr != nil { + return nil, httpErr } identity := common.ComputeIdentity(identityPrefix[:], newSigner.From) diff --git a/tests/decrypt_commitment_test.go b/tests/decrypt_commitment_test.go index 0e8fbf0..f2f3b80 100644 --- a/tests/decrypt_commitment_test.go +++ b/tests/decrypt_commitment_test.go @@ -16,6 +16,7 @@ var msg = []byte("please hide this message") func (s *TestShutterService) TestDecryptionCommitmentNotFound() { ctx := context.Background() eon := rand.Int63() + blockNumber := rand.Uint64() identity, err := generateRandomBytes(32) s.Require().NoError(err) @@ -40,6 +41,16 @@ func (s *TestShutterService) TestDecryptionCommitmentNotFound() { }, nil). Once() + s.ethClient. + On("BlockNumber", ctx). + Return(blockNumber, nil). + Once() + + s.keyperSetManagerContract. + On("GetKeyperSetIndexByBlock", nil, blockNumber). + Return(uint64(eon), nil). + Once() + identityStringified := hex.EncodeToString(identity) encryptedCommitmentStringified := hex.EncodeToString(encrypedCommitmentBytes) _, err = s.cryptoUsecase.DecryptCommitment(ctx, encryptedCommitmentStringified, identityStringified, -1) @@ -49,6 +60,7 @@ func (s *TestShutterService) TestDecryptionCommitmentNotFound() { func (s *TestShutterService) TestDecryptionCommitment() { ctx := context.Background() eon := rand.Int63() + blockNumber := rand.Uint64() identity, err := generateRandomBytes(32) s.Require().NoError(err) @@ -75,6 +87,16 @@ func (s *TestShutterService) TestDecryptionCommitment() { }, nil). Once() + s.ethClient. + On("BlockNumber", ctx). + Return(blockNumber, nil). + Once() + + s.keyperSetManagerContract. + On("GetKeyperSetIndexByBlock", nil, blockNumber). + Return(uint64(eon), nil). + Once() + err = InsertDecryptionKey(ctx, s.testDB.DbInstance, eon, identity, decryptionKey.Marshal()) s.Require().NoError(err) diff --git a/tests/integration/shutter_service_test.go b/tests/integration/shutter_service_test.go index 44baf2c..22e3e90 100644 --- a/tests/integration/shutter_service_test.go +++ b/tests/integration/shutter_service_test.go @@ -60,7 +60,7 @@ func (s *TestShutterService) TestRequestDecryptionKeyBeforeTimestampReached() { time.Sleep(30 * time.Second) errorResponse := s.getDecryptionKeyRequestError(identityStringified, http.StatusBadRequest) - s.Require().Equal(errorResponse.Description, "timestamp not reached yet, decryption key requested too early") + s.Require().Equal("timestamp not reached yet, decryption key requested too early", errorResponse.Description) } func (s *TestShutterService) TestRequestDecryptionKeyAfterTimestampReached() { @@ -101,7 +101,9 @@ func (s *TestShutterService) TestRequestDecryptionKeyAfterTimestampReached() { block, err := s.ethClient.BlockByNumber(ctx, nil) s.Require().NoError(err) - decryptionTimestamp := block.Header().Time + 10 + // Use a timestamp that's far enough in the future to avoid timing issues + // but close enough that we don't have to wait too long + decryptionTimestamp := block.Header().Time + 40 reqBody := service.RegisterIdentityRequest{ DecryptionTimestamp: uint64(decryptionTimestamp), IdentityPrefix: identityPrefixStringified, @@ -111,7 +113,7 @@ func (s *TestShutterService) TestRequestDecryptionKeyAfterTimestampReached() { s.Require().NoError(err) s.registerIdentityRequest(jsonData, http.StatusOK) - time.Sleep(30 * time.Second) + time.Sleep(45 * time.Second) decryptionKeyResponse := s.getDecryptionKeyRequest(res.Identity, http.StatusOK) @@ -147,7 +149,7 @@ func (s *TestShutterService) TestRequestDecryptionKeyForUnregisteredIdentity() { s.Require().NotNil(res.IdentityPrefix) errorResponse := s.getDecryptionKeyRequestError(res.Identity, http.StatusBadRequest) - s.Require().Equal(errorResponse.Description, "identity has not been registerd yet") + s.Require().Equal("identity has not been registerd yet", errorResponse.Description) } func (s *TestShutterService) TestRequestDecryptCommitmentAfterTimestampReached() { @@ -188,7 +190,7 @@ func (s *TestShutterService) TestRequestDecryptCommitmentAfterTimestampReached() block, err := s.ethClient.BlockByNumber(ctx, nil) s.Require().NoError(err) - decryptionTimestamp := block.Header().Time + 10 + decryptionTimestamp := block.Header().Time + 20 reqBody := service.RegisterIdentityRequest{ DecryptionTimestamp: uint64(decryptionTimestamp), IdentityPrefix: identityPrefixStringified, @@ -198,10 +200,16 @@ func (s *TestShutterService) TestRequestDecryptCommitmentAfterTimestampReached() s.Require().NoError(err) s.registerIdentityRequest(jsonData, http.StatusOK) - time.Sleep(30 * time.Second) + time.Sleep(40 * time.Second) + reqBody = service.RegisterIdentityRequest{ + DecryptionTimestamp: uint64(decryptionTimestamp + 120), + IdentityPrefix: identityPrefixStringified, + } + jsonData, err = json.Marshal(reqBody) + s.Require().NoError(err) errRegisterIdentity := s.registerIdentityRequestError(jsonData, http.StatusBadRequest) - s.Require().Equal(errRegisterIdentity.Description, "identity already registered") + s.Require().Equal("identity already registered", errRegisterIdentity.Description) query := fmt.Sprintf("?identity=%s&encryptedCommitment=%s", res.Identity, encryptedCommitmentStringified) url := "/api/decrypt_commitment" + query @@ -254,7 +262,7 @@ func (s *TestShutterService) TestRegisterIdentityInThePast() { s.Require().NoError(err) errorResponse := s.registerIdentityRequestError(jsonData, http.StatusBadRequest) - s.Require().Equal(errorResponse.Description, "decryption timestamp should be in future") + s.Require().Equal("decryption timestamp should be in future", errorResponse.Description) } func (s *TestShutterService) TestBulkRequestDecryptionKeyAfterTimestampReached() { @@ -308,6 +316,11 @@ func (s *TestShutterService) TestBulkRequestDecryptionKeyAfterTimestampReached() jsonData, err := json.Marshal(reqBody) s.Require().NoError(err) + + // Add a small delay between registrations to prevent nonce conflicts + if i > 0 { + time.Sleep(5 * time.Second) + } s.registerIdentityRequest(jsonData, http.StatusOK) identities[i] = res.Identity @@ -333,7 +346,7 @@ func (s *TestShutterService) TestBulkRequestDecryptionKeyAfterTimestampReached() } func (s *TestShutterService) registerIdentityRequest(jsonData []byte, statusCode int) { - url := "/api/register_identity" + url := "/api/time/register_identity" req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData)) s.Require().NoError(err) @@ -347,7 +360,7 @@ func (s *TestShutterService) registerIdentityRequest(jsonData []byte, statusCode } func (s *TestShutterService) registerIdentityRequestError(jsonData []byte, statusCode int) httpError.Http { - url := "/api/register_identity" + url := "/api/time/register_identity" req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData)) s.Require().NoError(err) @@ -371,7 +384,7 @@ func (s *TestShutterService) registerIdentityRequestError(jsonData []byte, statu func (s *TestShutterService) getDataForEncryptionRequest(address string, identityPrefix string) map[string]usecase.GetDataForEncryptionResponse { query := fmt.Sprintf("?address=%s&identityPrefix=%s", address, identityPrefix) - url := "/api/get_data_for_encryption" + query + url := "/api/time/get_data_for_encryption" + query recorder := httptest.NewRecorder() s.router.ServeHTTP(recorder, httptest.NewRequest("GET", url, nil)) @@ -390,7 +403,7 @@ func (s *TestShutterService) getDataForEncryptionRequest(address string, identit func (s *TestShutterService) getDecryptionKeyRequestError(identity string, statusCode int) httpError.Http { query := fmt.Sprintf("?identity=%s", identity) - url := "/api/get_decryption_key" + query + url := "/api/time/get_decryption_key" + query recorder := httptest.NewRecorder() s.router.ServeHTTP(recorder, httptest.NewRequest("GET", url, nil)) @@ -409,7 +422,7 @@ func (s *TestShutterService) getDecryptionKeyRequestError(identity string, statu func (s *TestShutterService) getDecryptionKeyRequest(identity string, statusCode int) map[string]usecase.GetDecryptionKeyResponse { query := fmt.Sprintf("?identity=%s", identity) - url := "/api/get_decryption_key" + query + url := "/api/time/get_decryption_key" + query recorder := httptest.NewRecorder() s.router.ServeHTTP(recorder, httptest.NewRequest("GET", url, nil)) @@ -425,3 +438,261 @@ func (s *TestShutterService) getDecryptionKeyRequest(identity string, statusCode return decryptionKeyResponse } + +func (s *TestShutterService) TestCompileEventTriggerDefinition() { + if testing.Short() { + s.T().Skip("skipping integration test") + } + body := `{"contract": "0x4d6dd1382aa09be1d243f8960409a1ab3d913f43", "eventSig":"event Transfer(address indexed from, address indexed to, uint256 amount)","arguments": [{"name": "from", "op": "eq", "bytes": "0x9e13976721ebff885611c8391d9b02749c1283fa"},{"name": "amount", "op": "gte", "number": "1"}]}` + jsonData := []byte(body) + + response := s.compileEventTriggerDefinitionRequest(jsonData, http.StatusOK) + s.Require().NotEmpty(response.EventTriggerDefinition) + s.Require().True(strings.HasPrefix(response.EventTriggerDefinition, "0x")) +} + +func (s *TestShutterService) TestCompileEventTriggerDefinitionIncompleteRequest() { + if testing.Short() { + s.T().Skip("skipping integration test") + } + testCases := []struct { + name string + body string + expectedDesc string + expectedDetail string + }{ + { + name: "missing contract", + body: `{"eventSig":"event Transfer(address indexed from, address indexed to, uint256 amount)","arguments": [{"name": "from", "op": "eq", "bytes": "0x9e13976721ebff885611c8391d9b02749c1283fa"},{"name": "amount", "op": "gte", "number": "1"}]}`, + expectedDesc: "unable to parse event trigger definition", + expectedDetail: "Contract address empty", + }, + { + name: "missing event signature", + body: `{"contract": "0x4d6dd1382aa09be1d243f8960409a1ab3d913f43","arguments": [{"name": "from", "op": "eq", "bytes": "0x9e13976721ebff885611c8391d9b02749c1283fa"},{"name": "amount", "op": "gte", "number": "1"}]}`, + expectedDesc: "unable to parse event trigger definition", + expectedDetail: "No event signature given", + }, + } + + for _, testCase := range testCases { + s.Run(testCase.name, func() { + body := s.compileEventTriggerDefinitionRequestError([]byte(testCase.body), http.StatusBadRequest) + s.Require().Contains(body, testCase.expectedDesc) + s.Require().Contains(body, testCase.expectedDetail) + }) + } +} + +func (s *TestShutterService) TestGetDataForEncryptionEventMissingTriggerDefinition() { + if testing.Short() { + s.T().Skip("skipping integration test") + } + identityPrefix, err := generateRandomBytes(32) + s.Require().NoError(err) + identityPrefixStringified := hex.EncodeToString(identityPrefix) + + errResp := s.getEventDataForEncryptionRequestError(identityPrefixStringified, http.StatusBadRequest) + s.Require().Equal("triggerDefinition query parameter is required for event-based get_data_for_encryption", errResp.Metadata) + s.Require().Equal("query parameter not found", errResp.Description) +} + +func (s *TestShutterService) TestRegisterEventIdentityAndGetTriggerExpirationBlock() { + if testing.Short() { + s.T().Skip("skipping integration test") + } + body := `{"contract": "0x4d6dd1382aa09be1d243f8960409a1ab3d913f43", "eventSig":"event Transfer(address indexed from, address indexed to, uint256 amount)","arguments": [{"name": "from", "op": "eq", "bytes": "0x9e13976721ebff885611c8391d9b02749c1283fa"},{"name": "amount", "op": "gte", "number": "1"}]}` + jsonData := []byte(body) + + triggerDefinitionResp := s.compileEventTriggerDefinitionRequest(jsonData, http.StatusOK) + s.Require().NotEmpty(triggerDefinitionResp.EventTriggerDefinition) + + identityPrefix, err := generateRandomBytes(32) + s.Require().NoError(err) + identityPrefixStringified := hex.EncodeToString(identityPrefix) + + dataForEncryptionResponse := s.getEventDataForEncryptionRequest(triggerDefinitionResp.EventTriggerDefinition, identityPrefixStringified) + res := dataForEncryptionResponse["message"] + s.Require().GreaterOrEqual(res.Eon, uint64(1)) + s.Require().NotEmpty(res.EonKey) + s.Require().NotEmpty(res.Identity) + s.Require().Equal(common.PrefixWith0x(identityPrefixStringified), res.IdentityPrefix) + + reqBody := service.RegisterEventIdentityRequest{ + EventTriggerDefinitionHex: triggerDefinitionResp.EventTriggerDefinition, + IdentityPrefix: identityPrefixStringified, + Ttl: 10, + } + + registerJSON, err := json.Marshal(reqBody) + s.Require().NoError(err) + + registerResponse := s.registerEventIdentityRequest(registerJSON, http.StatusOK) + registerResult := registerResponse["message"] + s.Require().NotEmpty(registerResult.TxHash) + s.Require().Equal(res.IdentityPrefix, registerResult.IdentityPrefix) + s.Require().Equal(res.Eon, registerResult.Eon) + + var expirationResp usecase.GetEventTriggerExpirationBlockResponse + for i := 0; i < 15; i++ { + status, resp, _ := s.getEventTriggerExpirationBlockRequestWithStatus(registerResult.Eon, registerResult.IdentityPrefix) + if status == http.StatusOK && resp.ExpirationBlockNumber > 0 { + expirationResp = resp + break + } + time.Sleep(2 * time.Second) + } + + s.Require().Greater(expirationResp.ExpirationBlockNumber, uint64(0)) +} + +func (s *TestShutterService) TestGetEventDecryptionKeyInvalidIdentity() { + if testing.Short() { + s.T().Skip("skipping integration test") + } + errResp := s.getEventDecryptionKeyRequestError("0x11", http.StatusBadRequest) + s.Require().Equal("identity should be of length 32", errResp.Description) +} + +func (s *TestShutterService) compileEventTriggerDefinitionRequest(jsonData []byte, statusCode int) usecase.EventTriggerDefinitionResponse { + url := "/api/event/compile_trigger_definition" + + req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData)) + s.Require().NoError(err) + + req.Header.Set("Content-Type", "application/json") + + recorder := httptest.NewRecorder() + s.router.ServeHTTP(recorder, req) + s.Require().Equal(statusCode, recorder.Code) + + body, err := io.ReadAll(recorder.Body) + s.Require().NoError(err) + + var response usecase.EventTriggerDefinitionResponse + err = json.Unmarshal(body, &response) + s.Require().NoError(err) + + return response +} + +func (s *TestShutterService) compileEventTriggerDefinitionRequestError(jsonData []byte, statusCode int) string { + url := "/api/event/compile_trigger_definition" + + req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData)) + s.Require().NoError(err) + + req.Header.Set("Content-Type", "application/json") + + recorder := httptest.NewRecorder() + s.router.ServeHTTP(recorder, req) + s.Require().Equal(statusCode, recorder.Code) + + body, err := io.ReadAll(recorder.Body) + s.Require().NoError(err) + + return string(body) +} + +func (s *TestShutterService) getEventDataForEncryptionRequest(triggerDefinition string, identityPrefix string) map[string]usecase.GetDataForEncryptionResponse { + query := fmt.Sprintf("?triggerDefinition=%s&identityPrefix=%s", triggerDefinition, identityPrefix) + url := "/api/event/get_data_for_encryption" + query + + recorder := httptest.NewRecorder() + s.router.ServeHTTP(recorder, httptest.NewRequest("GET", url, nil)) + + s.Require().Equal(http.StatusOK, recorder.Code) + + body, err := io.ReadAll(recorder.Body) + s.Require().NoError(err) + + var dataForEncryptionResponse map[string]usecase.GetDataForEncryptionResponse + err = json.Unmarshal(body, &dataForEncryptionResponse) + s.Require().NoError(err) + + return dataForEncryptionResponse +} + +func (s *TestShutterService) getEventDataForEncryptionRequestError(identityPrefix string, statusCode int) httpError.Http { + query := fmt.Sprintf("?identityPrefix=%s", identityPrefix) + url := "/api/event/get_data_for_encryption" + query + + recorder := httptest.NewRecorder() + s.router.ServeHTTP(recorder, httptest.NewRequest("GET", url, nil)) + + s.Require().Equal(statusCode, recorder.Code) + + body, err := io.ReadAll(recorder.Body) + s.Require().NoError(err) + + var errorResponse httpError.Http + err = json.Unmarshal(body, &errorResponse) + s.Require().NoError(err) + + return errorResponse +} + +func (s *TestShutterService) registerEventIdentityRequest(jsonData []byte, statusCode int) map[string]usecase.RegisterIdentityResponse { + url := "/api/event/register_identity" + + req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData)) + s.Require().NoError(err) + + req.Header.Set("Content-Type", "application/json") + + recorder := httptest.NewRecorder() + s.router.ServeHTTP(recorder, req) + s.Require().Equal(statusCode, recorder.Code) + + body, err := io.ReadAll(recorder.Body) + s.Require().NoError(err) + + var registerResponse map[string]usecase.RegisterIdentityResponse + err = json.Unmarshal(body, ®isterResponse) + s.Require().NoError(err) + + return registerResponse +} + +func (s *TestShutterService) getEventTriggerExpirationBlockRequestWithStatus(eon uint64, identityPrefix string) (int, usecase.GetEventTriggerExpirationBlockResponse, httpError.Http) { + query := fmt.Sprintf("?eon=%d&identityPrefix=%s", eon, identityPrefix) + url := "/api/event/get_trigger_expiration_block" + query + + recorder := httptest.NewRecorder() + s.router.ServeHTTP(recorder, httptest.NewRequest("GET", url, nil)) + + body, err := io.ReadAll(recorder.Body) + s.Require().NoError(err) + + if recorder.Code == http.StatusOK { + var response map[string]usecase.GetEventTriggerExpirationBlockResponse + err = json.Unmarshal(body, &response) + s.Require().NoError(err) + return recorder.Code, response["message"], httpError.Http{} + } + + var errorResponse httpError.Http + err = json.Unmarshal(body, &errorResponse) + s.Require().NoError(err) + + return recorder.Code, usecase.GetEventTriggerExpirationBlockResponse{}, errorResponse +} + +func (s *TestShutterService) getEventDecryptionKeyRequestError(identity string, statusCode int) httpError.Http { + query := fmt.Sprintf("?identity=%s", identity) + url := "/api/event/get_decryption_key" + query + + recorder := httptest.NewRecorder() + s.router.ServeHTTP(recorder, httptest.NewRequest("GET", url, nil)) + + s.Require().Equal(statusCode, recorder.Code) + + body, err := io.ReadAll(recorder.Body) + s.Require().NoError(err) + + var errorResponse httpError.Http + err = json.Unmarshal(body, &errorResponse) + s.Require().NoError(err) + + return errorResponse +}