public async Task SubmitRecoveryShare()

in src/ccf/recovery/ccf-recovery-agent/Controllers/RecoveryMembersController.cs [231:328]


    public async Task<IActionResult> SubmitRecoveryShare(
        [FromRoute] string memberId,
        [FromBody] byte[] content)
    {
        // Verify caller is an active member of the consortium.
        (var err, var payload) = await this.VerifyMemberAuthentication(memberId, content);
        if (err != null)
        {
            return this.BadRequest(err);
        }

        var input = JsonSerializer.Deserialize<SubmitRecoveryShareInput>(payload)!;
        err = ValidateInput(input);
        if (err != null)
        {
            return this.BadRequest(err);
        }

        this.logger.LogInformation(
            $"Requesting recovery service for recovery share for member {input.MemberName}.");

        var wsConfig = await this.clientManager.GetWsConfig();

        // To complete recovery share submission the steps are:
        // - Get encrypted share from CCF
        // - Request recovery service to decrypt and return the recovery_share message
        //   which would then be submitted to CCF to get the state digest.
        // - Submit recovery_share message to CCF.
        var svcClient = await this.GetRecoverySvcClient(input.AgentConfig.RecoveryService);

        var recoveryMember = (await svcClient.GetFromJsonAsync<JsonObject>(
            $"members/{input.MemberName}"))!;
        var signingCert = recoveryMember["signingCert"]!.ToString();
        using var cert = X509Certificate2.CreateFromPem(signingCert);
        var recoveryMemberId = cert.GetCertHashString(HashAlgorithmName.SHA256).ToLower();

        var ccfClient = await this.clientManager.GetCcfClient();
        var encryptedShare = await ccfClient.GetFromJsonAsync<JsonObject>(
            $"/gov/recovery/encrypted-shares/{recoveryMemberId}" +
            $"?api-version={this.clientManager.GetGovApiVersion()}");

        // Get attestation report and encrypted share content to send in the request.
        var dataContent = new JsonObject
        {
            ["memberName"] = input.MemberName,
            ["encryptedShare"] = encryptedShare
        };
        (var data, var signature) =
            this.PrepareSignedData(dataContent, wsConfig.Attestation.PrivateKey);
        var svcContent = Attestation.PrepareSignedDataRequestContent(
            data,
            signature,
            wsConfig.Attestation.PublicKey,
            wsConfig.Attestation.Report);

        byte[] recoveryShareMessage;
        using (var genRecoveryShareResponse = await svcClient.PostAsync(
            $"members/generateRecoveryShareMessage",
            JsonContent.Create(svcContent)))
        {
            await genRecoveryShareResponse.ValidateStatusCodeAsync(this.logger);

            var body = (await genRecoveryShareResponse.Content.ReadFromJsonAsync<JsonObject>())!;
            var wrappedValue = Convert.FromBase64String(body["message"]!.ToString());
            recoveryShareMessage = Attestation.UnwrapRsaOaepAesKwpValue(
                wrappedValue,
                wsConfig.Attestation.PrivateKey);
        }

        JsonObject? recoveryShareResponse;
        using (HttpRequestMessage request = Cose.CreateHttpRequestMessage(
            $"gov/recovery/members/{recoveryMemberId}:recover" +
            $"?api-version={this.clientManager.GetGovApiVersion()}",
            recoveryShareMessage))
        {
            using HttpResponseMessage response = await ccfClient.SendAsync(request);
            await response.ValidateStatusCodeAsync(this.logger);
            await response.WaitGovTransactionCommittedAsync(this.logger, ccfClient);
            recoveryShareResponse = await response.Content.ReadFromJsonAsync<JsonObject>();
        }

        return this.Ok(recoveryShareResponse);

        static ODataError? ValidateInput(SubmitRecoveryShareInput input)
        {
            if (string.IsNullOrEmpty(input.MemberName))
            {
                return new ODataError("InputMissing", "name input is required.");
            }

            if (input.AgentConfig == null)
            {
                return new ODataError("InputMissing", "agentConfig input is required.");
            }

            return null;
        }
    }