swift Gemini Private API

g6baxovj  于 2023-10-15  发布在  Swift
关注(0)|答案(1)|浏览(108)

我在使用Swift访问私有API端点时遇到问题。我试过使用Python,因为我很有经验,我能够从API中提取数据。我不知道发生了什么。Gemini API在他们的文档中没有任何Swift实现,所以很难弄清楚。我也试过使用ChatGPT,但我仍然有问题。

func generateNonce() -> String {
    let timestamp = String(Int(Date().timeIntervalSince1970 * 1000)) // Get the current timestamp in milliseconds
    let uniqueId = UUID().uuidString // Generate a unique identifier
    // Concatenate the timestamp and unique identifier to create the nonce
    let nonce = timestamp + uniqueId
    return nonce
}

func signPayload(payload: String, secret: String) -> String {
    if let payloadData = payload.data(using: .utf8), let secretData = secret.data(using: .utf8) {
        var result = [UInt8](repeating: 0, count: Int(CC_SHA384_DIGEST_LENGTH))
        payloadData.withUnsafeBytes { payloadPtr in
            secretData.withUnsafeBytes { secretPtr in
                CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA384), secretPtr.baseAddress, secretData.count, payloadPtr.baseAddress, payloadData.count, &result)
            }
        }
        
        let signatureData = Data(result)
        let signature = signatureData.map { String(format: "%02x", $0) }.joined()
        
        return signature
    } else {
        return ""
    }
}

func base64Encode(payload: String) -> String? {
    if let payloadData = payload.data(using: .utf8) {
        return payloadData.base64EncodedString()
    }
    return nil
}

let apiKey = MyAPIKEY
let apiSecret = MYAPISecret
let baseUrl = "https://api.gemini.com/v1/notionalbalances/usd"
let endpoint = "/v1/notionalbalances/usd"
let account = "primary"

func performAPICall() async throws -> Crypto {
    
    let url = URL(string: baseUrl)!
    
    let nonce = generateNonce()
    let payload = "\(nonce)+\(endpoint)+\(account)"
//    let payload = "["request": "/v1/notionalbalances/usd", "nonce" : nonce, "account" : "primary"]"
//    let payload = """
//    {
//        "request": "/v1/notionalbalances/usd",
//        "nonce": "\(nonce)",
//        "account": "\(account)"
//    }
//    """
    
    let signature = signPayload(payload: payload, secret: apiSecret)

    var request = URLRequest(url:url)
    request.httpMethod = "POST"
    
    request.setValue("text/plain", forHTTPHeaderField: "Content-Type")
    request.setValue("0", forHTTPHeaderField: "Content-Length")
    request.setValue(apiKey, forHTTPHeaderField: "X-GEMINI-APIKEY")
    request.setValue(base64Encode(payload: payload), forHTTPHeaderField: "X-GEMINI-PAYLOAD")
    request.setValue(signature, forHTTPHeaderField: "X-GEMINI-SIGNATURE")
    request.setValue("no-cache", forHTTPHeaderField: "Cache-Control")
    
//    request.setValue(generateNonce(), forHTTPHeaderField: "nonce")
    
    let (data, response) = try await URLSession.shared.data(from: url)
    
    if let httpResponse = response as? HTTPURLResponse {
                print("Status Code: \(httpResponse.statusCode)")
        print("Response Headers: \(httpResponse.allHeaderFields)")
            }
    
    let wrapper = try JSONDecoder().decode(Crypto.self, from: data)
    return wrapper
}

Task {
    var crypto = try await performAPICall()
}

我不断得到这个错误:
验证码:404响应标题:[AnyHashable(“Vary”):Origin,AnyHashable(“Content-Length”):111,AnyHashable(“服务器”):nginx,AnyHashable(“Content-Type”):application/json,AnyHashable(“Date”):星期六,2023年9月30日16:21:07 GMT]

72qzrwbm

72qzrwbm1#

在文档中,它说X-GEMINI-SIGNATURE头必须是hex(HMAC_SHA384(base64(payload), key=api_secret)),但这不是您的代码发送的内容。
您发送hex(HMAC_SHA384(payload, key=api_secret)),然后对在X-GEMINI-PAYLOAD报头中发送的有效负载进行base64编码。
另外,你的有效载荷不正确。您正在使用"\(nonce)+\(endpoint)+\(account)"-它将类似于<nonce>/v1/notionalbalances/usd<account>,但它需要是一个JSON文档。
您创建nonce的方式也可能不符合文档。它指出:
如果你没有选择基于时间的随机数,那么随机数必须是一个永远不会重复的数字,并且必须在请求之间增加。
虽然你使用时间戳作为nonce的第一部分,所以它总是会增加,doucumentation说它需要一个整数(大概是以10为底),所以我不认为160+位的十六进制值会起作用。使用时间戳可能更安全。
你也要去所有的麻烦,设置您的URLRequest,但然后不使用它。您需要调用URLSession.shared.data(for: request),而不是URLSession.shared.data(from: url)
建议代码:

enum GeminiError: Error {
    case signatureFailureError
}

struct NotionalBalancePayload: Codable {
   let request: String
   let nonce: Int
   let account: string?
}

func signPayload(payload: String, secret: String) -> String? {
    guard let payloadData = payload.data(using:.utf8), 
          let secretData = secret.data(using: .utf8) else {
        return nil
    }
    var result = [UInt8](repeating: 0, count: Int(CC_SHA384_DIGEST_LENGTH))
    payloadData.withUnsafeBytes { payloadPtr in
            secretData.withUnsafeBytes { secretPtr in
                CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA384), secretPtr.baseAddress, secretData.count, payloadPtr.baseAddress, payloadData.count, &result)
    }
        
    let signature = Data(result).{ String(format: "%02x", $0) }.joined()
        
    return signature
}

let apiKey = MyAPIKEY
let apiSecret = MYAPISecret
let baseUrl = "https://api.gemini.com/v1/notionalbalances/usd"
let endpoint = "/v1/notionalbalances/usd"
let account = "primary"

func performAPICall() async throws -> Crypto {
    
    let url = URL(string: baseUrl)!
    
    let notionalBalancePayload = NotionalBalancePayload(request: endpoint, nonce: Int(Date().timeIntervalSince1970))

    let payload = try JSONEncoder.encode(notionalBalancePayload).base64EncodedString()
    
    if let signature = signPayload(payload: payload, secret: apiSecret) {

        var request = URLRequest(url:url)
        request.httpMethod = "POST"
    
        request.setValue("text/plain", forHTTPHeaderField: "Content-Type")
        request.setValue("0", forHTTPHeaderField: "Content-Length")
        request.setValue(apiKey, forHTTPHeaderField: "X-GEMINI-APIKEY")
        request.setValue(payload, forHTTPHeaderField: "X-GEMINI-PAYLOAD")
        request.setValue(signature, forHTTPHeaderField: "X-GEMINI-SIGNATURE")
        request.setValue("no-cache", forHTTPHeaderField: "Cache-Control")
    
        let (data, response) = try await URLSession.shared.data(for: request)
    
        if let httpResponse = response as? HTTPURLResponse {
                print("Status Code: \(httpResponse.statusCode)")
            print("Response Headers: \(httpResponse.allHeaderFields)")
        }
    
        let wrapper = try JSONDecoder().decode(Crypto.self, from: data)
        return wrapper 
    } else {
         // Throw an error
         throw GeminiError.signatureFailureError
    }
}

相关问题