Skip to content

Commit 1688716

Browse files
Merge pull request #15 from BitHighlander/feat/zcash-seed-fingerprint
feat(zcash): seed_fingerprint client + tests
2 parents d4eda86 + 69d28d6 commit 1688716

7 files changed

Lines changed: 324 additions & 24 deletions

File tree

.gitmodules

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[submodule "device-protocol"]
22
path = device-protocol
3-
url = https://github.com/keepkey/device-protocol.git
3+
url = https://github.com/BitHighlander/device-protocol.git
44
branch = master
55
[submodule "keepkeylib/eth/ethereum-lists"]
66
path = keepkeylib/eth/ethereum-lists

keepkeylib/client.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1661,10 +1661,28 @@ def ton_sign_tx(self, address_n, raw_tx):
16611661

16621662
# ── Zcash Address Display ─────────────────────────────────
16631663
@expect(zcash_proto.ZcashAddress)
1664-
def zcash_display_address(self, address_n, address, ak, nk, rivk, account=None):
1664+
def zcash_display_address(self, address_n, address, ak, nk, rivk,
1665+
account=None, expected_seed_fingerprint=None):
1666+
"""Display a Zcash unified address on the device for user confirmation.
1667+
1668+
Args:
1669+
address_n: ZIP-32 derivation path [32', 133', account']
1670+
address: unified address string ("u1...")
1671+
ak, nk, rivk: 32-byte FVK components for verification
1672+
account: account index (alternative to full path)
1673+
expected_seed_fingerprint: optional 32-byte ZIP-32 §6.1 seed
1674+
fingerprint. If provided, device verifies the match before
1675+
displaying and rejects with Failure on mismatch.
1676+
1677+
Returns:
1678+
ZcashAddress with .address and .seed_fingerprint of the
1679+
attesting device.
1680+
"""
16651681
kwargs = dict(address_n=address_n, address=address, ak=ak, nk=nk, rivk=rivk)
16661682
if account is not None:
16671683
kwargs['account'] = account
1684+
if expected_seed_fingerprint is not None:
1685+
kwargs['expected_seed_fingerprint'] = expected_seed_fingerprint
16681686
return self.call(zcash_proto.ZcashDisplayAddress(**kwargs))
16691687

16701688
# ── Zcash Orchard ──────────────────────────────────────────
@@ -1681,7 +1699,8 @@ def zcash_sign_pczt(self, address_n, actions, account=None,
16811699
header_digest=None, transparent_digest=None,
16821700
sapling_digest=None, orchard_digest=None,
16831701
orchard_flags=None, orchard_value_balance=None,
1684-
orchard_anchor=None, transparent_inputs=None):
1702+
orchard_anchor=None, transparent_inputs=None,
1703+
expected_seed_fingerprint=None):
16851704
"""Sign a Zcash Orchard shielded transaction via PCZT protocol.
16861705
16871706
Phase 2: Sends ZcashSignPCZT, then loops on ZcashPCZTActionAck
@@ -1737,6 +1756,8 @@ def zcash_sign_pczt(self, address_n, actions, account=None,
17371756
kwargs['orchard_value_balance'] = orchard_value_balance
17381757
if orchard_anchor is not None:
17391758
kwargs['orchard_anchor'] = orchard_anchor
1759+
if expected_seed_fingerprint is not None:
1760+
kwargs['expected_seed_fingerprint'] = expected_seed_fingerprint
17401761

17411762
resp = self.call(zcash_proto.ZcashSignPCZT(**kwargs))
17421763

keepkeylib/messages_zcash_pb2.py

Lines changed: 49 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

keepkeylib/zcash.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""Zcash helpers for client-side computations.
2+
3+
Mirrors the firmware's ZIP-32 §6.1 seed fingerprint so callers can build the
4+
expected_seed_fingerprint they pass to display/sign messages without having to
5+
ask the device.
6+
"""
7+
8+
from hashlib import blake2b
9+
10+
11+
_PERSONAL = b"Zcash_HD_Seed_FP"
12+
13+
14+
def calculate_seed_fingerprint(seed):
15+
"""Compute the ZIP-32 §6.1 seed fingerprint.
16+
17+
SeedFingerprint := BLAKE2b-256(
18+
"Zcash_HD_Seed_FP", I2LEBSP_8(len(seed)) || seed
19+
)
20+
21+
The 1-byte length prefix domain-separates seeds of different lengths
22+
that happen to share a prefix; per the spec.
23+
24+
Args:
25+
seed: bytes, length 32-252.
26+
27+
Returns:
28+
32-byte fingerprint.
29+
30+
Raises:
31+
ValueError: if seed length is out of range or the seed is trivially
32+
all-zero or all-0xFF (matches firmware's rejection per §6.1).
33+
"""
34+
if not isinstance(seed, (bytes, bytearray)):
35+
raise TypeError("seed must be bytes")
36+
if len(seed) < 32 or len(seed) > 252:
37+
raise ValueError("seed length must be in [32, 252]")
38+
if all(b == 0x00 for b in seed) or all(b == 0xFF for b in seed):
39+
raise ValueError("trivial seed (all-zero or all-0xFF) rejected")
40+
41+
h = blake2b(digest_size=32, person=_PERSONAL)
42+
h.update(bytes([len(seed)]))
43+
h.update(bytes(seed))
44+
return h.digest()

0 commit comments

Comments
 (0)