Module: WaveDispatch
- Defined in:
- lib/wave_dispatch.rb
Defined Under Namespace
Classes: Client
Constant Summary collapse
- VERSION =
"0.6.3"
Class Method Summary collapse
-
.sign_cdp_jwt(creds, accept) ⇒ Object
0.6.2 — CDP-JWT (ES256/P-256) signing via OpenSSL stdlib.
-
.wallet_sign(provider, creds, challenge) ⇒ Object
Built-in provider sign — HTTP orchestration only; actual signing happens at the provider.
Class Method Details
.sign_cdp_jwt(creds, accept) ⇒ Object
0.6.2 — CDP-JWT (ES256/P-256) signing via OpenSSL stdlib. Header: kid:<api_key>, typ:‘JWT’, nonce:<rand-hex16>. Payload: iss:‘cdp’, nbf:<now>, exp:<now+120>, uri:‘POST dispatch.wave.online<resource>’, claim:<accept>. ECDSA signs the base64url(header).base64url(payload) input; the DER signature is unpacked to raw 32+32 r||s (IEEE P-1363), which is exactly what JWS ES256 expects.
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/wave_dispatch.rb', line 139 def self.sign_cdp_jwt(creds, accept) require "openssl" require "securerandom" nonce = SecureRandom.hex(16) now = Time.now.to_i uri = "POST dispatch.wave.online#{(accept || {})["resource"] || "/"}" header = { alg: "ES256", kid: creds[:api_key], typ: "JWT", nonce: nonce } payload = { sub: creds[:api_key], iss: "cdp", nbf: now, exp: now + 120, uri: uri, claim: accept } b64url = ->(s) { Base64.urlsafe_encode64(s).gsub("=", "") } to_sign = b64url.call(header.to_json) + "." + b64url.call(payload.to_json) key = OpenSSL::PKey.read(creds[:api_secret]) raise "dispatch.wallet_hook(cdp): api_secret must be an EC P-256 private key" unless key.is_a?(OpenSSL::PKey::EC) der_sig = key.sign(OpenSSL::Digest.new("SHA256"), to_sign) # DER → raw r||s. OpenSSL ASN1 parses the SEQUENCE into [r, s] big-integers. asn1 = OpenSSL::ASN1.decode(der_sig) r = asn1.value[0].value.to_s(2).rjust(32, "\x00".b) s = asn1.value[1].value.to_s(2).rjust(32, "\x00".b) to_sign + "." + b64url.call(r + s) end |
.wallet_sign(provider, creds, challenge) ⇒ Object
Built-in provider sign — HTTP orchestration only; actual signing happens at the provider.
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/wave_dispatch.rb', line 92 def self.wallet_sign(provider, creds, challenge) accepts = challenge.is_a?(Hash) ? (challenge["accepts"] || []) : [] accept = accepts.find { |a| a.is_a?(Hash) && a["protocol"] == provider.to_s } || accepts.first || {} case provider when :cdp # 0.6.2 — real CDP-JWT (ES256/P-256) signing via OpenSSL stdlib. creds: {api_key, api_secret (PEM EC), # address?, network?}. The signed JWT is the cdp-payment header value; WAVE's CDP verifier validates # the signature on its side. No optional dep needed — Ruby's stdlib OpenSSL handles everything. raise "dispatch.wallet_hook(cdp): api_key required" unless creds[:api_key] raise "dispatch.wallet_hook(cdp): api_secret required (PEM EC private key)" unless creds[:api_secret] jwt = WaveDispatch.sign_cdp_jwt(creds, accept) { provider: "cdp", jwt: jwt, address: creds[:address], network: creds[:network] || "base", accept: accept }.to_json when :privy %i[app_id app_secret wallet_id].each { |k| raise "dispatch.wallet_hook(privy): #{k} required" unless creds[k] } basic = Base64.strict_encode64("#{creds[:app_id]}:#{creds[:app_secret]}") uri = URI("https://auth.privy.io/api/v1/wallets/#{URI.encode_www_form_component(creds[:wallet_id])}/rpc") req = Net::HTTP::Post.new(uri, "content-type" => "application/json", "authorization" => "Basic #{basic}", "privy-app-id" => creds[:app_id]) req.body = { method: "personal_sign", params: { message: accept.to_json }, chain_type: "ethereum" }.to_json res = Net::HTTP.start(uri.host, uri.port, use_ssl: true, verify_mode: OpenSSL::SSL::VERIFY_PEER) { |h| h.request(req) } raise "dispatch.wallet_hook(privy): provider #{res.code}" unless res.code.start_with?("2") j = (JSON.parse(res.body) rescue {}) sig = (j.dig("data", "signature") || j["signature"]) { provider: "privy", signature: sig, accept: accept }.to_json when :bridge raise "dispatch.wallet_hook(bridge): :api_key required" unless creds[:api_key] uri = URI("https://api.bridge.xyz/v0/transfers") req = Net::HTTP::Post.new(uri, "content-type" => "application/json", "api-key" => creds[:api_key]) req.body = { source: creds[:source_wallet], destination: creds[:destination] || accept["payTo"], amount: accept["maxAmountRequired"] }.to_json res = Net::HTTP.start(uri.host, uri.port, use_ssl: true, verify_mode: OpenSSL::SSL::VERIFY_PEER) { |h| h.request(req) } raise "dispatch.wallet_hook(bridge): provider #{res.code}" unless res.code.start_with?("2") j = (JSON.parse(res.body) rescue {}) { provider: "bridge", id: j["id"], accept: accept }.to_json else raise "dispatch.wallet_sign: unsupported provider #{provider.inspect}" end end |