internal static void ApplySignature(ProtectedTask task, byte[] data, byte[] signature) { task.Signature = CryptoServices.EncodePrefixed(HashType.Signature, signature); using (var buffer = new MemoryStream()) { buffer.Write(data, 0, data.Length); buffer.Write(signature, 0, signature.Length); task.SignedOperation = buffer.ToArray().ToHexString(); } }
// The signing function _____________________________________________________________________ internal async Task <bool> Sign(ProtectedTask task, Identity signingIdentity) { Approval approval = null; // Find signing provider var provider = signProviders .FirstOrDefault(p => p.Contains(signingIdentity.AccountID)); if (provider == null) { Trace($"No provider found for {signingIdentity}"); return(false); } Trace($"Operation: {task.Operation}"); // Fetch operation data to sign later. // This prevents tampering with the operation during approval var operationData = task.Operation.HexToByteArray(); // Check validity of operation data // This protects against forgery due to a compromised node ValidateOperation(task, operationData); // Approval mechanism registered? if (ApprovalRequired != null) { approval = new Approval { Task = task, Signer = signingIdentity, Timeout = ApprovalTimeout, }; // Send approval to UI and wait for completion await ExecuteSynchronized(() => { ApprovalRequired(approval); }); WaitForUser: Trace("Wait for user decision"); var userResult = await approval.GetUserResult(); Trace($"Approval result: {userResult}"); if (userResult != SigningResult.Approved) { // Cancel or timeout approval.Close(userResult); return(false); } // Unlock identity if needed if (signingIdentity.IsLocked) { if (!signingIdentity.Unlock(approval.Passphrase)) { // Wrong credentials approval.Retry(SigningResult.InvalidCredentials); goto WaitForUser; } } } // Sign Trace("Sign CreateRequest"); var dataToHash = new byte[1 + operationData.Length]; dataToHash[0] = 3; operationData.CopyTo(dataToHash, 1); var hashedData = CryptoServices.Hash(dataToHash, 32); if (await provider.Sign(signingIdentity.AccountID, hashedData, out byte[] signature)) { // Success task.Signature = CryptoServices.EncodePrefixed(HashType.Signature, signature); using (var buffer = new MemoryStream()) { buffer.Write(operationData, 0, operationData.Length); buffer.Write(signature, 0, signature.Length); task.SignedOperation = buffer.ToArray().ToHexString(); } approval?.Close(SigningResult.Signed); return(true); }
private void ValidateOperation(ProtectedTask task, byte[] operationData) { if (configuration.InTestMode) { return; } void Fail(string reason) { var exception = new ApplicationException($"Validation | {reason}"); Trace(exception); throw exception; } var parsed = new ParsedOperation(operationData); // Only main and service item if (parsed.Transfers.Count != 2) { Fail("Too many transfers in operation"); } // Service fee as offered to user var serviceTransfer = parsed.Transfers[1]; if (serviceTransfer.Amount != task.ServiceFee) { Fail("Wrong service fee"); } // Network fee as offered to user var mainTransfer = parsed.Transfers[0]; if (mainTransfer.Fee != task.NetworkFee) { Fail("Wrong network fee"); } // Transfer amount if (mainTransfer.Amount != task.TransferAmount) { Fail("Wrong amount"); } // Task specific validation switch (task) { case TransferTask transfer: { if (mainTransfer.Kind != "Transfer") { Fail("Wrong task kind"); } if (mainTransfer.DestinationID != transfer.DestinationID) { Fail("Wrong transfer destination"); } } break; case CreateContractTask originate: { if (mainTransfer.Kind != "Origination") { Fail("Wrong task kind"); } } break; default: break; } }