synor/sdk/ruby/lib/synor_privacy/client.rb
Gulshan Yadav e65ea40af2 feat: implement Privacy and Contract SDKs for all 12 languages (Phase 5)
Privacy SDK features:
- Confidential transactions with Pedersen commitments
- Bulletproof range proofs for value validation
- Ring signatures for anonymous signing with key images
- Stealth addresses for unlinkable payments
- Blinding factor generation and value operations

Contract SDK features:
- Smart contract deployment (standard and CREATE2)
- Call (view/pure) and Send (state-changing) operations
- Event log filtering, subscription, and decoding
- ABI encoding/decoding utilities
- Gas estimation and contract verification
- Multicall for batched operations
- Storage slot reading

Languages implemented:
- JavaScript/TypeScript
- Python (async with httpx)
- Go
- Rust (async with reqwest/tokio)
- Java (async with OkHttp)
- Kotlin (coroutines with Ktor)
- Swift (async/await with URLSession)
- Flutter/Dart
- C (header-only interface)
- C++ (header-only with std::future)
- C#/.NET (async with HttpClient)
- Ruby (Faraday HTTP client)

All SDKs follow consistent patterns:
- Configuration with API key, endpoint, timeout, retries
- Custom exception types with error codes
- Retry logic with exponential backoff
- Health check endpoints
- Closed state management
2026-01-28 09:03:34 +05:30

269 lines
8 KiB
Ruby

# frozen_string_literal: true
require "faraday"
require "json"
require "uri"
module SynorPrivacy
# Synor Privacy SDK client for Ruby.
# Confidential transactions, ring signatures, stealth addresses, and cryptographic commitments.
class Client
attr_reader :closed
def initialize(config)
@config = config
@closed = false
@conn = Faraday.new(url: config.endpoint) do |f|
f.request :json
f.response :json
f.options.timeout = config.timeout
f.headers["Authorization"] = "Bearer #{config.api_key}"
f.headers["Content-Type"] = "application/json"
f.headers["X-SDK-Version"] = "ruby/#{VERSION}"
end
end
# ==================== Confidential Transactions ====================
def create_confidential_tx(inputs:, outputs:)
body = { inputs: inputs.map(&:to_h), outputs: outputs.map(&:to_h) }
response = post("/privacy/confidential/create", body)
parse_confidential_transaction(response)
end
def verify_confidential_tx(tx:)
body = { transaction: tx.to_h }
response = post("/privacy/confidential/verify", body)
response["valid"] == true
end
def create_commitment(value:, blinding_factor:)
body = { value: value, blinding_factor: blinding_factor }
response = post("/privacy/commitment/create", body)
Commitment.new(
commitment: response["commitment"],
blinding_factor: response["blinding_factor"]
)
end
def verify_commitment(commitment:, value:, blinding_factor:)
body = { commitment: commitment, value: value, blinding_factor: blinding_factor }
response = post("/privacy/commitment/verify", body)
response["valid"] == true
end
def create_range_proof(value:, blinding_factor:, min_value:, max_value:)
body = {
value: value,
blinding_factor: blinding_factor,
min_value: min_value,
max_value: max_value
}
response = post("/privacy/range-proof/create", body)
RangeProof.new(
proof: response["proof"],
commitment: response["commitment"],
min_value: response["min_value"],
max_value: response["max_value"]
)
end
def verify_range_proof(proof:)
body = { proof: proof.to_h }
response = post("/privacy/range-proof/verify", body)
response["valid"] == true
end
# ==================== Ring Signatures ====================
def create_ring_signature(message:, ring:, signer_index:, private_key:)
body = {
message: message,
ring: ring,
signer_index: signer_index,
private_key: private_key
}
response = post("/privacy/ring/sign", body)
RingSignature.new(
c0: response["c0"],
s: response["s"],
key_image: response["key_image"],
ring: response["ring"]
)
end
def verify_ring_signature(signature:, message:)
body = { signature: signature.to_h, message: message }
response = post("/privacy/ring/verify", body)
response["valid"] == true
end
def generate_decoys(count, exclude_key: nil)
path = "/privacy/ring/decoys?count=#{count}"
path += "&exclude=#{encode(exclude_key)}" if exclude_key
get(path)
end
def check_key_image(key_image)
response = get("/privacy/ring/key-image/#{key_image}")
response["spent"] == true
end
# ==================== Stealth Addresses ====================
def generate_stealth_keypair
response = post("/privacy/stealth/generate", {})
StealthKeyPair.new(
spend_public_key: response["spend_public_key"],
spend_private_key: response["spend_private_key"],
view_public_key: response["view_public_key"],
view_private_key: response["view_private_key"]
)
end
def derive_stealth_address(spend_public_key:, view_public_key:)
body = { spend_public_key: spend_public_key, view_public_key: view_public_key }
response = post("/privacy/stealth/derive", body)
StealthAddress.new(
address: response["address"],
ephemeral_public_key: response["ephemeral_public_key"],
tx_public_key: response["tx_public_key"]
)
end
def recover_stealth_private_key(stealth_address:, view_private_key:, spend_private_key:)
body = {
stealth_address: stealth_address,
view_private_key: view_private_key,
spend_private_key: spend_private_key
}
response = post("/privacy/stealth/recover", body)
response["private_key"]
end
def scan_outputs(view_private_key:, spend_public_key:, from_block:, to_block: nil)
body = {
view_private_key: view_private_key,
spend_public_key: spend_public_key,
from_block: from_block
}
body[:to_block] = to_block if to_block
response = post("/privacy/stealth/scan", body)
response.map { |o| parse_stealth_output(o) }
end
# ==================== Blinding ====================
def generate_blinding_factor
response = post("/privacy/blinding/generate", {})
response["blinding_factor"]
end
def blind_value(value:, blinding_factor:)
body = { value: value, blinding_factor: blinding_factor }
response = post("/privacy/blinding/blind", body)
response["blinded_value"]
end
def unblind_value(blinded_value:, blinding_factor:)
body = { blinded_value: blinded_value, blinding_factor: blinding_factor }
response = post("/privacy/blinding/unblind", body)
response["value"]
end
# ==================== Lifecycle ====================
def health_check
response = get("/health")
response["status"] == "healthy"
rescue StandardError
false
end
def close
@closed = true
@conn.close if @conn.respond_to?(:close)
end
private
def get(path, params = {})
execute { @conn.get(path, params).body }
end
def post(path, body)
execute { @conn.post(path, body).body }
end
def execute
raise ClientClosedError, "Client has been closed" if @closed
last_error = nil
@config.retries.times do |attempt|
begin
response = yield
check_error(response) if response.is_a?(Hash)
return response
rescue StandardError => e
last_error = e
sleep(2**attempt) if attempt < @config.retries - 1
end
end
raise last_error
end
def check_error(response)
return unless response["error"] || (response["code"] && response["message"])
message = response["message"] || response["error"] || "Unknown error"
code = response["code"]
status = response["status_code"] || 0
raise HttpError.new(message, status_code: status, code: code)
end
def encode(str)
URI.encode_www_form_component(str)
end
def parse_confidential_transaction(data)
ConfidentialTransaction.new(
id: data["id"],
inputs: (data["inputs"] || []).map { |i| parse_confidential_input(i) },
outputs: (data["outputs"] || []).map { |o| parse_confidential_output(o) },
fee: data["fee"],
excess: data["excess"],
excess_sig: data["excess_sig"],
kernel_offset: data["kernel_offset"]
)
end
def parse_confidential_input(data)
ConfidentialTxInput.new(
commitment: data["commitment"],
blinding_factor: data["blinding_factor"],
value: data["value"],
key_image: data["key_image"]
)
end
def parse_confidential_output(data)
ConfidentialTxOutput.new(
commitment: data["commitment"],
blinding_factor: data["blinding_factor"],
value: data["value"],
recipient_public_key: data["recipient_public_key"],
range_proof: data["range_proof"]
)
end
def parse_stealth_output(data)
StealthOutput.new(
tx_hash: data["tx_hash"],
output_index: data["output_index"],
stealth_address: data["stealth_address"],
amount: data["amount"],
block_height: data["block_height"]
)
end
end
end