Skip to content

Commit e15abe7

Browse files
committed
fix(bip137): add checks for recid on sign message
This commit add checks for signing message UI using recid following spec from BIP137.
1 parent 63e8b8e commit e15abe7

2 files changed

Lines changed: 93 additions & 12 deletions

File tree

src/krux/bip137.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# The MIT License (MIT)
2+
3+
# Copyright (c) 2021-2026 Krux contributors
4+
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
12+
# The above copyright notice and this permission notice shall be included in
13+
# all copies or substantial portions of the Software.
14+
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
# THE SOFTWARE.
22+
23+
P2PKH_UNCOMPRESSED_HEADER = 27
24+
P2PKH_HEADER = 31
25+
P2SH_P2WPKH_HEADER = 35
26+
P2WPKH_HEADER = 39
27+
MESSAGE_MAGIC = b"\x18Bitcoin Signed Message:\n"
28+
RECID_OFFSET = P2PKH_HEADER - P2PKH_UNCOMPRESSED_HEADER
29+
30+
31+
def message_commitment(message):
32+
"""Double-SHA256 commitment over the BIP-137 magic"""
33+
from embit import compact
34+
35+
try:
36+
import uhashlib as hashlib
37+
except ImportError:
38+
import hashlib
39+
40+
# BIP137 commitment message:
41+
# `SHA256(SHA256(MAGIC // varint // message))`
42+
varint = compact.to_bytes(len(message))
43+
_message = MESSAGE_MAGIC + varint + message
44+
return hashlib.sha256(hashlib.sha256(_message).digest()).digest()
45+
46+
47+
def build_header(raw_sig, script_type, compressed=True):
48+
"""Build header byte from raw signature and script_type"""
49+
# Avoid some unexpected header
50+
rsig = raw_sig[0]
51+
if not P2PKH_UNCOMPRESSED_HEADER <= rsig <= P2PKH_HEADER + 3:
52+
raise ValueError("Invalid sig header: %d" % rsig)
53+
54+
# grab the 2 least significant bits as recId
55+
# and normalize with a minimum p2pkh (uncompressed) flag
56+
recid = (raw_sig[0] - P2PKH_UNCOMPRESSED_HEADER) & 3
57+
58+
if script_type == "p2sh-p2wpkh":
59+
return P2SH_P2WPKH_HEADER + recid
60+
if script_type == "p2wpkh":
61+
return P2WPKH_HEADER + recid
62+
63+
# compressed=False is only meaningful for p2pkh
64+
return (
65+
P2PKH_UNCOMPRESSED_HEADER
66+
+ recid
67+
+ (0 if not compressed and script_type == "p2pkh" else RECID_OFFSET)
68+
)
69+
70+
71+
def sign(message, key, derivation, script_type="p2pkh", compressed=True):
72+
"""Sign a BIP137 `message` with `key` at `derivation` for some `script_type`"""
73+
commitment = message_commitment(message)
74+
raw_sig = key.sign_at(derivation, commitment)
75+
header = build_header(raw_sig, script_type, compressed)
76+
sig = bytes([header]) + raw_sig[1:]
77+
return (commitment, sig)

src/krux/pages/home_pages/sign_message_ui.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2121
# THE SOFTWARE.
2222

23-
from embit import bip32, compact, script
23+
from embit import bip32, script
2424
from embit.networks import NETWORKS
2525
import hashlib
2626
import binascii
@@ -133,7 +133,8 @@ def get_bitcoin_address(self, derivation_path, script_type=""):
133133
return addr
134134

135135
def _sign_at_address(self, message, derivation_str, address=""):
136-
"""Signs a message at a derived Bitcoin address"""
136+
"""Signs a BIP137 message at a derived Bitcoin address"""
137+
from krux import bip137
137138

138139
derivation = bip32.parse_path(derivation_str)
139140
self._display_message_sign_prompt(
@@ -143,15 +144,8 @@ def _sign_at_address(self, message, derivation_str, address=""):
143144
if not self.prompt(t("Sign?"), BOTTOM_PROMPT_LINE):
144145
return None
145146

146-
message_hash = hashlib.sha256(
147-
hashlib.sha256(
148-
b"\x18Bitcoin Signed Message:\n"
149-
+ compact.to_bytes(len(message))
150-
+ message
151-
).digest()
152-
).digest()
153-
154-
sig = self.ctx.wallet.key.sign_at(derivation, message_hash)
147+
script_type = self.get_script_type_from_path(derivation_str) or "p2pkh"
148+
_, sig = bip137.sign(message, self.ctx.wallet.key, derivation, script_type)
155149
self._display_signature(base_encode(sig, 64))
156150
return sig
157151

@@ -234,6 +228,8 @@ def _sign_at_address_from_sd(self, data):
234228

235229
def sign_standard_message(self, data):
236230
"""Signs a standard message"""
231+
from krux import bip137
232+
237233
message_hash, is_raw_hash = self._compute_message_hash(data)
238234
if message_hash is None:
239235
return ""
@@ -248,6 +244,8 @@ def sign_standard_message(self, data):
248244
)
249245
if not self.prompt(t("Proceed?"), BOTTOM_PROMPT_LINE):
250246
return ""
247+
else:
248+
message_hash = bip137.message_commitment(data)
251249

252250
self.ctx.display.clear()
253251
self.ctx.display.draw_centered_text(
@@ -257,7 +255,13 @@ def sign_standard_message(self, data):
257255
if not self.prompt(t("Sign?"), BOTTOM_PROMPT_LINE):
258256
return ""
259257

260-
sig = self.ctx.wallet.key.sign(message_hash).serialize()
258+
key = self.ctx.wallet.key
259+
if is_raw_hash:
260+
sig = key.sign(message_hash).serialize()
261+
else:
262+
_, sig = bip137.sign(
263+
data, key, bip32.parse_path(key.derivation), key.script_type
264+
)
261265
self._display_signature(base_encode(sig, 64))
262266
return sig
263267

0 commit comments

Comments
 (0)