Disclaimer
Issue was found and fixed by LLM, but I manually verified both possible solutions.
Summary
jadepy serial RPCs can block until the configured serial timeout expires, even when the full Jade reply has already arrived.
On Arch Linux, this regression is reproducible with the system package python-cbor2 5.8.0-2 and disappears again after downgrading system-wide to python-cbor2 5.7.1.
The underlying issue appears to be that jadepy exposes the serial transport as a file-like object for cbor2.load(self), while JadeSerialImpl.read(n) forwards large read requests directly to pyserial.Serial.read(n). When cbor2 requests a large read, pyserial waits for either the full requested size or timeout, which delays delivery of much smaller Jade replies until timeout expiry.
Impact
This currently makes Jade effectively unusable without a system-wide cbor2 downgrade on at least some rolling-release distributions, with Arch Linux confirmed.
In practice this breaks downstream integrations such as Electrum's Jade support unless the user either:
- applies a local hotfix to the vendored
jadepy copy
- downgrades the system
python-cbor2 package to 5.7.1
Symptoms
In the affected environment, RPC latency tracks the configured serial timeout exactly:
timeout=1 -> reply arrives after about 1s
timeout=3 -> reply arrives after about 3s
timeout=10 -> reply arrives after about 10s
This was reproducible with:
ping()
get_version_info()
- Electrum's Jade initialization path, especially the follow-up
add_entropy() call after reconnect
Example test script:
from time import perf_counter
from jadepy import JadeAPI
with JadeAPI.create_serial("/dev/ttyACM0", timeout=10) as jade:
t0 = perf_counter()
print(jade.get_version_info())
print(perf_counter() - t0)
The device responds correctly, but only after the timeout interval.
Environment observed
- Arch Linux system packages
- Python
3.14.3
python-pyserial 3.5-8
python-cbor2 5.8.0-2
- electrum
4.7.1-1
- Jade detected at
/dev/ttyACM0
Also relevant:
- downgrading from Arch
python-cbor2 5.8.0-2 to 5.7.1 avoids the issue without any Jade-side hotfix
- the current Electrum AppImage did not show the problem
- this repository already pins
cbor2==5.7.1 and mentions an existing cbor2 issue in requirements.txt
This suggests the bug is exposed by newer cbor2 behavior, but the actual compatibility problem is in jadepy serial adapter semantics.
Root cause analysis
The problematic chain is:
jadepy decodes replies with cbor.load(self)
- the file-like object delegates reads to
JadeInterface.read(n)
- the serial backend delegates that unchanged to
JadeSerialImpl.read(n)
JadeSerialImpl.read(n) currently does:
def read(self, n):
assert self.ser is not None
return self.ser.read(n)
- in affected environments,
cbor2.load(self) asks for large chunks, observed in logs as read(4096)
pyserial.Serial.read(4096) waits for either 4096 bytes or timeout
- Jade replies are much smaller than
4096 bytes
- result: replies are delivered only when timeout expires
This does not appear to be primarily an Electrum bug. Electrum only makes the issue very visible because it:
- first connects with
timeout=1 for probing
- then reconnects with the default timeout
- then calls
add_entropy()
So the second phase can appear to hang for the full default timeout.
Evidence from downgrade and downstream hotfix
Two independent mitigations were tested and both resolved the issue.
1. System-wide cbor2 downgrade
Downgrading the Arch package from:
to:
avoided the problem without any local Jade patch.
2. Local jadepy serial hotfix
A local hotfix that changed the vendored jade_serial.read() implementation to avoid blindly reading the full requested size also fixed the issue immediately.
Hotfix logic:
def read(self, n):
assert self.ser is not None
waiting = getattr(self.ser, "in_waiting", 0) or 0
if waiting > 0:
return self.ser.read(min(n, waiting))
return self.ser.read(1)
After this change:
- Electrum detected Jade normally
- RPCs returned promptly
- the scanning problem disappeared
Taken together, this strongly suggests that cbor2 5.8.0 exposes a transport-layer compatibility bug in jadepy rather than introducing a Jade-specific functional regression by itself.
Proposed fix
Fix jadepy serial transport so that its read(n) implementation does not block waiting for the full requested size when used as a file-like source for cbor2.
A transport-level fix in jadepy/jade_serial.py seems like the right place.
Possible approach:
- if bytes are already waiting, read up to
min(n, in_waiting)
- otherwise read a small amount, for example
1, to preserve blocking semantics without forcing exact-size waits on large n
This would make jadepy robust against decoder implementations that request large read sizes.
Suggested regression test
Add a regression test covering the interaction between:
cbor.load(self)
JadeInterface.read(n)
- serial transport semantics
The test should simulate a small CBOR reply and a decoder path that requests a large read size, and verify that the reply does not wait for the serial timeout.
Downstream follow-up
Electrum vendors a copy of jadepy under:
electrum/plugins/jade/jadepy/
After this is fixed upstream in Jade, the vendored copy in Electrum should also be updated so downstream users get the fix.
Disclaimer
Issue was found and fixed by LLM, but I manually verified both possible solutions.
Summary
jadepyserial RPCs can block until the configured serial timeout expires, even when the full Jade reply has already arrived.On Arch Linux, this regression is reproducible with the system package
python-cbor2 5.8.0-2and disappears again after downgrading system-wide topython-cbor2 5.7.1.The underlying issue appears to be that
jadepyexposes the serial transport as a file-like object forcbor2.load(self), whileJadeSerialImpl.read(n)forwards large read requests directly topyserial.Serial.read(n). Whencbor2requests a large read,pyserialwaits for either the full requested size or timeout, which delays delivery of much smaller Jade replies until timeout expiry.Impact
This currently makes Jade effectively unusable without a system-wide
cbor2downgrade on at least some rolling-release distributions, with Arch Linux confirmed.In practice this breaks downstream integrations such as Electrum's Jade support unless the user either:
jadepycopypython-cbor2package to5.7.1Symptoms
In the affected environment, RPC latency tracks the configured serial timeout exactly:
timeout=1-> reply arrives after about1stimeout=3-> reply arrives after about3stimeout=10-> reply arrives after about10sThis was reproducible with:
ping()get_version_info()add_entropy()call after reconnectExample test script:
The device responds correctly, but only after the timeout interval.
Environment observed
3.14.3python-pyserial 3.5-8python-cbor2 5.8.0-24.7.1-1/dev/ttyACM0Also relevant:
python-cbor2 5.8.0-2to5.7.1avoids the issue without any Jade-side hotfixcbor2==5.7.1and mentions an existingcbor2issue inrequirements.txtThis suggests the bug is exposed by newer
cbor2behavior, but the actual compatibility problem is injadepyserial adapter semantics.Root cause analysis
The problematic chain is:
jadepydecodes replies withcbor.load(self)JadeInterface.read(n)JadeSerialImpl.read(n)JadeSerialImpl.read(n)currently does:cbor2.load(self)asks for large chunks, observed in logs asread(4096)pyserial.Serial.read(4096)waits for either4096bytes or timeout4096bytesThis does not appear to be primarily an Electrum bug. Electrum only makes the issue very visible because it:
timeout=1for probingadd_entropy()So the second phase can appear to hang for the full default timeout.
Evidence from downgrade and downstream hotfix
Two independent mitigations were tested and both resolved the issue.
1. System-wide
cbor2downgradeDowngrading the Arch package from:
python-cbor2 5.8.0-2to:
python-cbor2 5.7.1-2avoided the problem without any local Jade patch.
2. Local
jadepyserial hotfixA local hotfix that changed the vendored
jade_serial.read()implementation to avoid blindly reading the full requested size also fixed the issue immediately.Hotfix logic:
After this change:
Taken together, this strongly suggests that
cbor2 5.8.0exposes a transport-layer compatibility bug injadepyrather than introducing a Jade-specific functional regression by itself.Proposed fix
Fix
jadepyserial transport so that itsread(n)implementation does not block waiting for the full requested size when used as a file-like source forcbor2.A transport-level fix in
jadepy/jade_serial.pyseems like the right place.Possible approach:
min(n, in_waiting)1, to preserve blocking semantics without forcing exact-size waits on largenThis would make
jadepyrobust against decoder implementations that request large read sizes.Suggested regression test
Add a regression test covering the interaction between:
cbor.load(self)JadeInterface.read(n)The test should simulate a small CBOR reply and a decoder path that requests a large read size, and verify that the reply does not wait for the serial timeout.
Downstream follow-up
Electrum vendors a copy of
jadepyunder:electrum/plugins/jade/jadepy/After this is fixed upstream in Jade, the vendored copy in Electrum should also be updated so downstream users get the fix.