-
-
Notifications
You must be signed in to change notification settings - Fork 671
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce Nostr #4436
base: main
Are you sure you want to change the base?
Introduce Nostr #4436
Conversation
|
de9d4e4
to
bd13fed
Compare
9ef8c6a
to
feabb03
Compare
We should check that the Also I prefer changing the CLI interface from using Otherwise great work! 🎉 |
core/src/apps/bitcoin/keychain.py
Outdated
@@ -91,6 +91,9 @@ class MsgWithAddressScriptType(Protocol): | |||
# SLIP-44 coin type for all Testnet coins | |||
SLIP44_TESTNET = const(1) | |||
|
|||
# SLIP-44 "coin type" for Nostr | |||
SLIP44_NOSTR = const(1237) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems this is not used anywhere.
content = msg.content | ||
|
||
node = keychain.derive(address_n) | ||
pk = node.public_key()[-32:] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we do this? Don't we care about the public key parity? Or does nostr use x-only keys?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we do this? Don't we care about the public key parity? Or does nostr use x-only keys?
From what I understand, Nostr uses X-only keys, indeed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But it is not guaranteed that keychain.derive
returns an x-only key, right?
I think we need to convert the key if a non x-only key is returned.
d66d0af
to
8bc5409
Compare
Yup! dc1a257#diff-3dd8aea4110592e07746c9d3233736246c7083e001c9d29db7c1b03f7423f93bR19 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some points for discussion.
[no changelog]
[no changelog]
@@ -0,0 +1 @@ | |||
Add Nostr support. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pls mark it somehow so that we know that it's debug-only
@@ -0,0 +1,5 @@ | |||
from apps.common.paths import PATTERN_BIP44 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
first entry here should be something like
if not __debug__:
from trezor.utils import halt
halt("disabled in production mode")
see apps/benchmark/__init__.py
|
||
|
||
@auto_keychain(__name__) | ||
async def get_pubkey(msg: NostrGetPubkey, keychain: Keychain) -> NostrPubkey: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
aside: there's no showing the Nostr key on screen, will that be added in the future? if not, this is useless because you can get the same pubkey via GetPublicKey
@@ -95,6 +95,7 @@ async def handle_session(iface: WireInterface) -> None: | |||
except Exception as exc: | |||
# Log and ignore. The session handler can only exit explicitly in the | |||
# following finally block. | |||
do_not_restart = True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please remove this change.
(if there's a reason you want this, please make a separate issue and a separate PR, this is nothing to do with Nostr)
@@ -62,7 +62,9 @@ def encode(self, msg: protobuf.MessageType) -> Tuple[int, bytes]: | |||
""" | |||
wire_type = self.class_to_type_override.get(type(msg), msg.MESSAGE_WIRE_TYPE) | |||
if wire_type is None: | |||
raise ValueError("Cannot encode class without wire type") | |||
raise ValueError( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make a separate commit for this one
|
||
|
||
@pytest.mark.setup_client(mnemonic=LEAD_MONKEY_MNEMONIC) | ||
def test_sign_event_lead_monkey(client: Client): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there's a better way to write these tests:
LEAD_MONKEY = pytest.mark.setup_client(...)
WHAT_BLEAK = pytest.mark.setup_client(...)
VECTORS = ( # pubkey_hex
pytest.param("cafecacebeef", mark=LEAD_MONKEY),
pytest.param(...)
)
@pytest.mark.parametrize("pubkey_hex", VECTORS)
def test_get_pubkey(client, pubkey_hex):
...
dtto for sign_event
except i'd prefer also hardcoding the signature bytes, in addition to verifying
response = nostr.sign_event( | ||
client, | ||
event=json.dumps( | ||
{"created_at": created_at, "kind": kind, "tags": tags, "content": content} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead, make a static event definition with a fixed time etc. on top level. we want the tests to be deterministic if we at all can
n: "Address", | ||
event: AnyStr, | ||
) -> "MessageType": | ||
event_json = json.loads(event) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you want a structured dict in, pass it in as a dict, not as a string. Let the caller do their own json.loads
if they want to. for bonus points, use TypedDict
.
(wondering if we should instead introduce a dataclass for the parameters, or perhaps use the protobuf message directly? given that we're returning the corresponding protobuf instance too)
|
||
|
||
@expect(messages.NostrPubkey) | ||
def get_pubkey( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this function should return just bytes
of the public key.
perhaps it's a good idea to rebase on #4490
|
||
address_n = tools.parse_path(PATH_TEMPLATE.format(account)) | ||
|
||
res = nostr.get_pubkey( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just return nostr.get_pubkey
, which should be changed to return bytes
per the other comment
This PR adds Nostr support to the Trezor firmware,
trezorlib
andtrezorctl
.Firmware
The Trezor firmware accepts the
NostrGetPubkey
andNostrSignEvent
messages, as defined in common/protob/messages-nostr.proto.To avoid adding complexity to the firmware, it does not parse the event JSON. The event needs to be parsed beforehand and its individual fields passed via the protobuf message. The return value contains the pubkey, event ID and signature, which need to be added back to the JSON by the caller before forwarding the event to relays.
UI
A simple UI exists to confirm the event being signed on your Trezor's screen. It will be improved later, but for now it just shows all the fields of the event being signed:
Trezor Safe 5
Main screen shows the event kind and the content:
From the menu you can view the
created_at
and the tags:Trezor Safe 3
Trezor Model T
trezorctl
trezorctl
accepts thenostr get-pubkey
andnostr sign-event
commands which correspond to the protobuf messages described above.Note: by passing
--account
(which defaults to0
- the "first" account) totrezorctl nostr get-pubkey
ortrezorctl nostr sign-event
you can generate a virtually infinite amount of Nostr keypairs from your Trezor - all derived from your seed phrase using them/44'/1237'/<account>'/0/0
derivation path, as defined by NIP-06.get-pubkey
Example:
ibz@localhost:~/dev/trezor-firmware$ trezorctl nostr get-pubkey
Output:
pubkey: 2dc0dd32b499f7bd428156334c3431893c18e6016cf1bf00187d76513368dba7
sign-event
trezorctl nostr sign-event
will parse the event JSON, send theNostrSignEvent
to the Trezor device and re-assemble the event JSON with theid
,pubkey
andsig
obtained from the Trezor.Example:
ibz@localhost:~/dev/trezor-firmware$ trezorctl nostr sign-event "{\"kind\": 1, \"created_at\": 1733917737, \"tags\": [[\"e\", \"5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36\", \"wss://nostr.example.com\"], [\"p\", \"f7234bd4c1394dda46d09f35bd384dd30cc552ad5541990f98844fb06676e9ca\"], [\"a\", \"30023:f7234bd4c1394dda46d09f35bd384dd30cc552ad5541990f98844fb06676e9ca:abcd\", \"wss://nostr.example.com\"], [\"alt\", \"reply\"]], \"content\": \"Hello world\"}"
Output:
signed_event: {"kind": 1, "created_at": 1733917737, "tags": [["e", "5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36", "wss://nostr.example.com"], ["p", "f7234bd4c1394dda46d09f35bd384dd30cc552ad5541990f98844fb06676e9ca"], ["a", "30023:f7234bd4c1394dda46d09f35bd384dd30cc552ad5541990f98844fb06676e9ca:abcd", "wss://nostr.example.com"], ["alt", "reply"]], "content": "Hello world", "id": "c2ea8f08093caebf6d73bfe4edbb7e8825bac8eb4308ab634e23161db18b78f8", "pubkey": "2dc0dd32b499f7bd428156334c3431893c18e6016cf1bf00187d76513368dba7", "sig": "4176eaa45730a617f7393ba53df009278504911b3b30d35e125cb1c0f0d1e92102f468705eaed7d3ae5bb3bb4267d9411f0184a7ab6c451110553c2fca97b39c"}