Skip to content

Commit 8ad8400

Browse files
djberg96ueno
authored andcommitted
Fix require.
1 parent cbaf9d9 commit 8ad8400

2 files changed

Lines changed: 178 additions & 1 deletion

File tree

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# -*- encoding: utf-8 -*-
2+
# Benchmark to investigate encryption vs encryption+signing performance
3+
require_relative '../test/test_helper'
4+
5+
# Simple timing helper since benchmark is no longer in stdlib
6+
def measure(label, n = 5)
7+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
8+
n.times { yield }
9+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
10+
puts "#{label.ljust(30)} #{elapsed.round(4)}s total, #{(elapsed / n).round(4)}s per iteration"
11+
elapsed
12+
end
13+
14+
# Profiling helper to trace where time is spent
15+
def profile_encrypt_sign
16+
plain_text = "Hello, World! " * 100
17+
crypto = GPGME::Crypto.new(always_trust: true)
18+
key = KEYS.first[:sha]
19+
20+
puts "\n=== Profiling encrypt only ==="
21+
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
22+
keys = GPGME::Key.find(:public, key)
23+
t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
24+
puts "Key.find for recipients: #{((t1 - t0) * 1000).round(2)}ms"
25+
26+
GPGME::Ctx.new(always_trust: true) do |ctx|
27+
t2 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
28+
puts "Ctx.new: #{((t2 - t1) * 1000).round(2)}ms"
29+
30+
plain_data = GPGME::Data.new(plain_text)
31+
cipher_data = GPGME::Data.new
32+
ctx.encrypt(keys, plain_data, cipher_data, GPGME::ENCRYPT_ALWAYS_TRUST)
33+
t3 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
34+
puts "encrypt: #{((t3 - t2) * 1000).round(2)}ms"
35+
end
36+
37+
puts "\n=== Profiling encrypt + sign (no explicit signer) ==="
38+
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
39+
keys = GPGME::Key.find(:public, key)
40+
t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
41+
puts "Key.find for recipients: #{((t1 - t0) * 1000).round(2)}ms"
42+
43+
GPGME::Ctx.new(always_trust: true) do |ctx|
44+
t2 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
45+
puts "Ctx.new: #{((t2 - t1) * 1000).round(2)}ms"
46+
47+
plain_data = GPGME::Data.new(plain_text)
48+
cipher_data = GPGME::Data.new
49+
ctx.encrypt_sign(keys, plain_data, cipher_data, GPGME::ENCRYPT_ALWAYS_TRUST)
50+
t3 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
51+
puts "encrypt_sign: #{((t3 - t2) * 1000).round(2)}ms"
52+
end
53+
54+
puts "\n=== Profiling encrypt + sign (with explicit signer - current code) ==="
55+
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
56+
keys = GPGME::Key.find(:public, key)
57+
t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
58+
puts "Key.find for recipients: #{((t1 - t0) * 1000).round(2)}ms"
59+
60+
signers = GPGME::Key.find(:public, key, :sign)
61+
t2 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
62+
puts "Key.find for signers: #{((t2 - t1) * 1000).round(2)}ms"
63+
64+
GPGME::Ctx.new(always_trust: true) do |ctx|
65+
t3 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
66+
puts "Ctx.new: #{((t3 - t2) * 1000).round(2)}ms"
67+
68+
ctx.add_signer(*signers)
69+
t4 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
70+
puts "add_signer: #{((t4 - t3) * 1000).round(2)}ms"
71+
72+
plain_data = GPGME::Data.new(plain_text)
73+
cipher_data = GPGME::Data.new
74+
ctx.encrypt_sign(keys, plain_data, cipher_data, GPGME::ENCRYPT_ALWAYS_TRUST)
75+
t5 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
76+
puts "encrypt_sign: #{((t5 - t4) * 1000).round(2)}ms"
77+
end
78+
79+
puts "\n=== Key.find cost breakdown ==="
80+
10.times do |i|
81+
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
82+
GPGME::Key.find(:public, key)
83+
t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
84+
puts "Key.find iteration #{i + 1}: #{((t1 - t0) * 1000).round(2)}ms"
85+
end
86+
end
87+
88+
# Ensure keys are imported
89+
import_keys
90+
91+
plain_text = "Hello, World! " * 1000 # ~14KB of data
92+
93+
crypto = GPGME::Crypto.new(always_trust: true)
94+
key = KEYS.first[:sha]
95+
96+
puts "Data size: #{plain_text.bytesize} bytes"
97+
puts
98+
99+
n = 50
100+
101+
measure("encrypt only:", n) do
102+
crypto.encrypt(plain_text, recipients: key)
103+
end
104+
105+
measure("encrypt + sign:", n) do
106+
crypto.encrypt(plain_text, recipients: key, sign: true)
107+
end
108+
109+
measure("sign only:", n) do
110+
crypto.sign(plain_text)
111+
end
112+
113+
puts
114+
puts "Running with explicit signer..."
115+
puts
116+
117+
measure("encrypt + sign (explicit):", n) do
118+
crypto.encrypt(plain_text, recipients: key, sign: true, signers: key)
119+
end
120+
121+
# Run detailed profiling
122+
profile_encrypt_sign

lib/gpgme/crypto.rb

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,9 @@ def encrypt(plain, options = {})
9191
begin
9292
if options[:sign]
9393
if options[:signers]
94-
signers = Key.find(:public, options[:signers], :sign)
94+
# Optimization: reuse recipient Key objects if signers match
95+
# to avoid redundant key lookups
96+
signers = resolve_keys_for_signing(options[:signers], keys)
9597
ctx.add_signer(*signers)
9698
end
9799
ctx.encrypt_sign(keys, plain_data, cipher_data, flags)
@@ -353,5 +355,58 @@ def self.method_missing(method, *args, &block)
353355
end
354356
end
355357

358+
private
359+
360+
# Resolves keys for signing, reusing already-fetched recipient keys when possible
361+
# to avoid redundant key lookups which can be slow.
362+
#
363+
# @param signers_input [Array, String, Key] The signer identifiers
364+
# @param recipient_keys [Array<Key>, nil] Already-fetched recipient keys to check for reuse
365+
# @return [Array<Key>] Keys suitable for signing
366+
def resolve_keys_for_signing(signers_input, recipient_keys)
367+
signers_input = [signers_input].flatten
368+
recipient_keys ||= []
369+
370+
# Build a lookup hash of recipient keys by various identifiers
371+
recipient_lookup = {}
372+
recipient_keys.each do |key|
373+
next unless key.is_a?(Key)
374+
# Index by fingerprint, short key ID, and email
375+
recipient_lookup[key.fingerprint] = key if key.fingerprint
376+
recipient_lookup[key.fingerprint[-16..]] = key if key.fingerprint && key.fingerprint.length >= 16
377+
recipient_lookup[key.fingerprint[-8..]] = key if key.fingerprint && key.fingerprint.length >= 8
378+
if key.uids && !key.uids.empty?
379+
key.uids.each do |uid|
380+
recipient_lookup[uid.email] = key if uid.email && !uid.email.empty?
381+
end
382+
end
383+
end
384+
385+
result = []
386+
needs_lookup = []
387+
388+
signers_input.each do |signer|
389+
case signer
390+
when Key
391+
# Already a key object, use directly if it can sign
392+
result << signer if signer.usable_for?([:sign])
393+
when String
394+
# Check if we already have this key from recipients
395+
if (existing = recipient_lookup[signer]) && existing.usable_for?([:sign])
396+
result << existing
397+
else
398+
needs_lookup << signer
399+
end
400+
end
401+
end
402+
403+
# Only do a Key.find for signers we couldn't resolve from recipients
404+
unless needs_lookup.empty?
405+
result += Key.find(:public, needs_lookup, :sign)
406+
end
407+
408+
result
409+
end
410+
356411
end # module Crypto
357412
end # module GPGME

0 commit comments

Comments
 (0)