diff --git a/common/protob/messages-nostr.proto b/common/protob/messages-nostr.proto new file mode 100644 index 00000000000..b56b6d8f368 --- /dev/null +++ b/common/protob/messages-nostr.proto @@ -0,0 +1,66 @@ +syntax = "proto2"; +package hw.trezor.messages.nostr; + +// Sugar for easier handling in Java +option java_package = "com.satoshilabs.trezor.lib.protobuf"; +option java_outer_classname = "TrezorMessageNostr"; + +import "options.proto"; + +/** + * Request: Ask the device for the Nostr public key + * @start + * @next NostrPubkey + */ +message NostrGetPubkey { + repeated uint32 address_n = 1; // used to derive the key +} + +/** + * Response: Nostr pubkey + * @end + */ +message NostrPubkey { + required bytes pubkey = 1; // pubkey derived from the seed +} + +/** + * @embed + */ +message NostrTag { + // Nostr tags consist of at least one string (the key) + // followed by an arbitrary number of strings, + // the first of which (if present) is called "value". + // See NIP-01: https://github.com/nostr-protocol/nips/blob/master/01.md#tags + required string key = 1; + optional string value = 2; + repeated string extra = 3; +} + +/** + * Request: Ask device to sign an event + * @start + * @next NostrEventSignature + * @next Failure + */ +message NostrSignEvent { + repeated uint32 address_n = 1; // used to derive the key + + // Nostr event fields, except the ones that are calculated by the signer + // See NIP-01: https://github.com/nostr-protocol/nips/blob/master/01.md + + required uint32 created_at = 2; // Event created_at: unix timestamp in seconds + required uint32 kind = 3; // Event kind: integer between 0 and 65535 + repeated NostrTag tags = 4; // Event tags + required string content = 5; // Event content: arbitrary string +} + +/** + * Response: Computed event ID and signature + * @end + */ +message NostrEventSignature { + required bytes pubkey = 1; // pubkey used to sign the event + required bytes id = 2; // ID of the event + required bytes signature = 3; // signature of the event +} diff --git a/common/protob/messages.proto b/common/protob/messages.proto index ae7fdfda599..e58b50cb6ba 100644 --- a/common/protob/messages.proto +++ b/common/protob/messages.proto @@ -79,7 +79,6 @@ enum MessageType { MessageType_NextU2FCounter = 81 [(wire_out) = true]; // Deprecated messages, kept for protobuf compatibility. - // Both are marked wire_out so that we don't need to implement incoming handler for legacy MessageType_Deprecated_PassphraseStateRequest = 77 [deprecated = true]; MessageType_Deprecated_PassphraseStateAck = 78 [deprecated = true]; @@ -324,6 +323,12 @@ enum MessageType { // THP reserved 1000 to 1099; // See messages-thp.proto + // Nostr + MessageType_NostrGetPubkey = 2001 [(wire_in) = true]; + MessageType_NostrPubkey = 2002 [(wire_out) = true]; + MessageType_NostrSignEvent = 2003 [(wire_in) = true]; + MessageType_NostrEventSignature = 2004 [(wire_out) = true]; + // Benchmark MessageType_BenchmarkListNames = 9100 [(bitcoin_only) = true]; MessageType_BenchmarkNames = 9101 [(bitcoin_only) = true]; diff --git a/core/.changelog.d/4160.added b/core/.changelog.d/4160.added new file mode 100644 index 00000000000..8b13ef64f98 --- /dev/null +++ b/core/.changelog.d/4160.added @@ -0,0 +1 @@ +Add Nostr support. diff --git a/core/SConscript.firmware b/core/SConscript.firmware index 74c30734152..790489b56c4 100644 --- a/core/SConscript.firmware +++ b/core/SConscript.firmware @@ -666,6 +666,11 @@ if FROZEN: SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/nem/*/*.py')) SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/enums/NEM*.py')) + if PYOPT == '0': + SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/nostr/*.py')) + SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/nostr/*/*.py')) + SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/enums/Nostr*.py')) + SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/ripple/*.py')) SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/enums/Ripple*.py')) diff --git a/core/SConscript.unix b/core/SConscript.unix index e4f1ca56b08..d40cb30b617 100644 --- a/core/SConscript.unix +++ b/core/SConscript.unix @@ -722,6 +722,11 @@ if FROZEN: SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/nem/*/*.py')) SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/enums/NEM*.py')) + if PYOPT == '0': + SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/nostr/*.py')) + SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/nostr/*/*.py')) + SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/enums/Nostr*.py')) + SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/ripple/*.py')) SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/enums/Ripple*.py')) diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h index 3197b9ff120..08ac2afa3d4 100644 --- a/core/embed/rust/librust_qstr.h +++ b/core/embed/rust/librust_qstr.h @@ -338,6 +338,7 @@ static void _librust_qstrs(void) { MP_QSTR_modify_fee__transaction_fee; MP_QSTR_more_info_callback; MP_QSTR_multiple_pages_texts; + MP_QSTR_nostr__event_kind_template; MP_QSTR_notification; MP_QSTR_notification_level; MP_QSTR_page_count; diff --git a/core/embed/rust/src/translations/generated/translated_string.rs b/core/embed/rust/src/translations/generated/translated_string.rs index e8da45f6ebc..c154f43d9c5 100644 --- a/core/embed/rust/src/translations/generated/translated_string.rs +++ b/core/embed/rust/src/translations/generated/translated_string.rs @@ -1382,6 +1382,8 @@ pub enum TranslatedString { misc__enable_labeling = 973, // "Enable labeling?" #[cfg(feature = "universal_fw")] ethereum__unknown_contract_address_short = 974, // "Unknown contract address." + #[cfg(feature = "universal_fw")] + nostr__event_kind_template = 975, // "Event kind: {0}" } impl TranslatedString { @@ -2758,6 +2760,8 @@ impl TranslatedString { Self::misc__enable_labeling => "Enable labeling?", #[cfg(feature = "universal_fw")] Self::ethereum__unknown_contract_address_short => "Unknown contract address.", + #[cfg(feature = "universal_fw")] + Self::nostr__event_kind_template => "Event kind: {0}", } } @@ -4135,6 +4139,8 @@ impl TranslatedString { Qstr::MP_QSTR_misc__enable_labeling => Some(Self::misc__enable_labeling), #[cfg(feature = "universal_fw")] Qstr::MP_QSTR_ethereum__unknown_contract_address_short => Some(Self::ethereum__unknown_contract_address_short), + #[cfg(feature = "universal_fw")] + Qstr::MP_QSTR_nostr__event_kind_template => Some(Self::nostr__event_kind_template), _ => None, } } diff --git a/core/embed/rust/src/translations/generated/translated_string.rs.mako b/core/embed/rust/src/translations/generated/translated_string.rs.mako index 9ecc94fbcff..84f8d7f9cdd 100644 --- a/core/embed/rust/src/translations/generated/translated_string.rs.mako +++ b/core/embed/rust/src/translations/generated/translated_string.rs.mako @@ -15,6 +15,7 @@ ALTCOIN_PREFIXES = ( "fido", "monero", "nem", + "nostr", "ripple", "solana", "stellar", diff --git a/core/mocks/trezortranslate_keys.pyi b/core/mocks/trezortranslate_keys.pyi index 8454099bfd9..22c5f7cf6ba 100644 --- a/core/mocks/trezortranslate_keys.pyi +++ b/core/mocks/trezortranslate_keys.pyi @@ -497,6 +497,7 @@ class TR: nem__under_namespace: str = "under namespace" nem__unencrypted: str = "Unencrypted:" nem__unknown_mosaic: str = "Unknown mosaic!" + nostr__event_kind_template: str = "Event kind: {0}" passphrase__access_wallet: str = "Access passphrase wallet?" passphrase__always_on_device: str = "Always enter your passphrase on Trezor?" passphrase__continue_with_empty_passphrase: str = "Continue with empty passphrase?" diff --git a/core/src/all_modules.py b/core/src/all_modules.py index 05f3a3330a5..ec663c1455b 100644 --- a/core/src/all_modules.py +++ b/core/src/all_modules.py @@ -415,6 +415,12 @@ import apps.misc.get_firmware_hash apps.misc.sign_identity import apps.misc.sign_identity +apps.nostr +import apps.nostr +apps.nostr.get_pubkey +import apps.nostr.get_pubkey +apps.nostr.sign_event +import apps.nostr.sign_event apps.workflow_handlers import apps.workflow_handlers diff --git a/core/src/apps/nostr/__init__.py b/core/src/apps/nostr/__init__.py new file mode 100644 index 00000000000..dbdba83a1f0 --- /dev/null +++ b/core/src/apps/nostr/__init__.py @@ -0,0 +1,5 @@ +from apps.common.paths import PATTERN_BIP44 + +CURVE = "secp256k1" +SLIP44_ID = 1237 +PATTERN = PATTERN_BIP44 diff --git a/core/src/apps/nostr/get_pubkey.py b/core/src/apps/nostr/get_pubkey.py new file mode 100644 index 00000000000..467f27cefc0 --- /dev/null +++ b/core/src/apps/nostr/get_pubkey.py @@ -0,0 +1,24 @@ +from typing import TYPE_CHECKING + +from apps.common.keychain import auto_keychain + +if TYPE_CHECKING: + from trezor.messages import NostrGetPubkey, NostrPubkey + + from apps.common.keychain import Keychain + + +@auto_keychain(__name__) +async def get_pubkey(msg: NostrGetPubkey, keychain: Keychain) -> NostrPubkey: + from trezor.messages import NostrPubkey + + from apps.common import paths + + address_n = msg.address_n + + await paths.validate_path(keychain, address_n) + + node = keychain.derive(address_n) + pk = node.public_key()[-32:] + + return NostrPubkey(pubkey=pk) diff --git a/core/src/apps/nostr/sign_event.py b/core/src/apps/nostr/sign_event.py new file mode 100644 index 00000000000..63fd12c39a6 --- /dev/null +++ b/core/src/apps/nostr/sign_event.py @@ -0,0 +1,60 @@ +from typing import TYPE_CHECKING + +from apps.common.keychain import auto_keychain + +if TYPE_CHECKING: + from trezor.messages import NostrEventSignature, NostrSignEvent + + from apps.common.keychain import Keychain + + +@auto_keychain(__name__) +async def sign_event(msg: NostrSignEvent, keychain: Keychain) -> NostrEventSignature: + from ubinascii import hexlify + + from trezor import TR + from trezor.crypto.curve import secp256k1 + from trezor.crypto.hashlib import sha256 + from trezor.messages import NostrEventSignature + from trezor.ui.layouts import confirm_value + + from apps.common import paths + + address_n = msg.address_n + created_at = msg.created_at + kind = msg.kind + tags = [[t.key] + ([t.value] if t.value else []) + t.extra for t in msg.tags] + content = msg.content + + await paths.validate_path(keychain, address_n) + + node = keychain.derive(address_n) + pk = node.public_key()[-32:] + sk = node.private_key() + + title = TR.nostr__event_kind_template.format(kind) + + # confirm_value on TR only accepts one single info item + # which is why we concatenate all of them here. + # This is not great, but it gets the job done for now. + tags_str = f"created_at: {created_at}" + for t in tags: + tags_str += f"\n\n{t[0]}: " + (f" {' '.join(t[1:])}" if len(t) > 1 else "") + + await confirm_value( + title, content, "", "nostr_sign_event", info_items=[("", tags_str)] + ) + + # The event ID is obtained by serializing the event in a specific way: + # "[0,pubkey,created_at,kind,tags,content]" + # See NIP-01: https://github.com/nostr-protocol/nips/blob/master/01.md + serialized_tags = ",".join( + ["[" + ",".join(f'"{t}"' for t in tag) + "]" for tag in tags] + ) + serialized_event = f'[0,"{hexlify(pk).decode()}",{created_at},{kind},[{serialized_tags}],"{content}"]' + event_id = sha256(serialized_event).digest() + + # The event signature is basically the signature of the event ID computed above + signature = secp256k1.sign(sk, event_id)[-64:] + + return NostrEventSignature(pubkey=pk, id=event_id, signature=signature) diff --git a/core/src/apps/workflow_handlers.py b/core/src/apps/workflow_handlers.py index b65c853c93f..c7ff7ba4772 100644 --- a/core/src/apps/workflow_handlers.py +++ b/core/src/apps/workflow_handlers.py @@ -106,6 +106,16 @@ def _find_message_handler_module(msg_type: int) -> str: if msg_type == MessageType.GetFirmwareHash: return "apps.misc.get_firmware_hash" + # When promoting the Nostr app to production-level + # and removing the "if" guard don't forget to also remove + # the corresponding guards (PYOPT == '0') in Sconscript.* + if __debug__: + # nostr + if msg_type == MessageType.NostrGetPubkey: + return "apps.nostr.get_pubkey" + if msg_type == MessageType.NostrSignEvent: + return "apps.nostr.sign_event" + if not utils.BITCOIN_ONLY: if msg_type == MessageType.SetU2FCounter: return "apps.management.set_u2f_counter" diff --git a/core/src/trezor/enums/MessageType.py b/core/src/trezor/enums/MessageType.py index ed569795b0b..14de4cf7904 100644 --- a/core/src/trezor/enums/MessageType.py +++ b/core/src/trezor/enums/MessageType.py @@ -250,3 +250,7 @@ SolanaAddress = 903 SolanaSignTx = 904 SolanaTxSignature = 905 + NostrGetPubkey = 2001 + NostrPubkey = 2002 + NostrSignEvent = 2003 + NostrEventSignature = 2004 diff --git a/core/src/trezor/enums/__init__.py b/core/src/trezor/enums/__init__.py index d16c3c4a660..1ed220d2ad5 100644 --- a/core/src/trezor/enums/__init__.py +++ b/core/src/trezor/enums/__init__.py @@ -591,6 +591,10 @@ class MessageType(IntEnum): SolanaAddress = 903 SolanaSignTx = 904 SolanaTxSignature = 905 + NostrGetPubkey = 2001 + NostrPubkey = 2002 + NostrSignEvent = 2003 + NostrEventSignature = 2004 BenchmarkListNames = 9100 BenchmarkNames = 9101 BenchmarkRun = 9102 diff --git a/core/src/trezor/messages.py b/core/src/trezor/messages.py index 2a3ce051256..a8e899839c0 100644 --- a/core/src/trezor/messages.py +++ b/core/src/trezor/messages.py @@ -5214,6 +5214,92 @@ def __init__( def is_type_of(cls, msg: Any) -> TypeGuard["NEMCosignatoryModification"]: return isinstance(msg, cls) + class NostrGetPubkey(protobuf.MessageType): + address_n: "list[int]" + + def __init__( + self, + *, + address_n: "list[int] | None" = None, + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["NostrGetPubkey"]: + return isinstance(msg, cls) + + class NostrPubkey(protobuf.MessageType): + pubkey: "bytes" + + def __init__( + self, + *, + pubkey: "bytes", + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["NostrPubkey"]: + return isinstance(msg, cls) + + class NostrTag(protobuf.MessageType): + key: "str" + value: "str | None" + extra: "list[str]" + + def __init__( + self, + *, + key: "str", + extra: "list[str] | None" = None, + value: "str | None" = None, + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["NostrTag"]: + return isinstance(msg, cls) + + class NostrSignEvent(protobuf.MessageType): + address_n: "list[int]" + created_at: "int" + kind: "int" + tags: "list[NostrTag]" + content: "str" + + def __init__( + self, + *, + created_at: "int", + kind: "int", + content: "str", + address_n: "list[int] | None" = None, + tags: "list[NostrTag] | None" = None, + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["NostrSignEvent"]: + return isinstance(msg, cls) + + class NostrEventSignature(protobuf.MessageType): + pubkey: "bytes" + id: "bytes" + signature: "bytes" + + def __init__( + self, + *, + pubkey: "bytes", + id: "bytes", + signature: "bytes", + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: Any) -> TypeGuard["NostrEventSignature"]: + return isinstance(msg, cls) + class RippleGetAddress(protobuf.MessageType): address_n: "list[int]" show_display: "bool | None" diff --git a/core/src/trezor/ui/layouts/mercury/__init__.py b/core/src/trezor/ui/layouts/mercury/__init__.py index 54b1e49b26c..62dea7c607c 100644 --- a/core/src/trezor/ui/layouts/mercury/__init__.py +++ b/core/src/trezor/ui/layouts/mercury/__init__.py @@ -623,9 +623,6 @@ def confirm_value( ) -> Awaitable[None]: """General confirmation dialog, used by many other confirm_* functions.""" - if not verb and not hold: - raise ValueError("Either verb or hold=True must be set") - info_items = info_items or [] info_layout = trezorui_api.show_info_with_cancel( title=info_title if info_title else TR.words__title_information, diff --git a/core/src/trezor/ui/layouts/tr/__init__.py b/core/src/trezor/ui/layouts/tr/__init__.py index 807baeac025..b498e283265 100644 --- a/core/src/trezor/ui/layouts/tr/__init__.py +++ b/core/src/trezor/ui/layouts/tr/__init__.py @@ -741,9 +741,6 @@ async def confirm_value( ) -> None: """General confirmation dialog, used by many other confirm_* functions.""" - if not verb and not hold: - raise ValueError("Either verb or hold=True must be set") - if info_items is None: return await raise_if_not_confirmed( trezorui_api.confirm_value( # type: ignore [Argument missing for parameter "subtitle"] @@ -761,6 +758,7 @@ async def confirm_value( else: info_items_list = list(info_items) if len(info_items_list) > 1: + # TODO: Support more than one info item! raise NotImplementedError("Only one info item is supported") send_button_request = True @@ -770,7 +768,7 @@ async def confirm_value( title=title, items=((ui.NORMAL, value),), button=verb or TR.buttons__confirm, - info_button=TR.buttons__info, + info_button=TR.buttons__info, # this is not used on TR, but required ), br_name if send_button_request else None, br_code, diff --git a/core/src/trezor/ui/layouts/tt/__init__.py b/core/src/trezor/ui/layouts/tt/__init__.py index 9248293c950..f161b40933f 100644 --- a/core/src/trezor/ui/layouts/tt/__init__.py +++ b/core/src/trezor/ui/layouts/tt/__init__.py @@ -676,9 +676,6 @@ def confirm_value( ) -> Awaitable[None]: """General confirmation dialog, used by many other confirm_* functions.""" - if not verb and not hold: - raise ValueError("Either verb or hold=True must be set") - info_items = info_items or [] info_layout = trezorui_api.show_info_with_cancel( title=info_title if info_title else TR.words__title_information, diff --git a/core/src/trezor/wire/__init__.py b/core/src/trezor/wire/__init__.py index 2662a5610aa..fc45ca9ba60 100644 --- a/core/src/trezor/wire/__init__.py +++ b/core/src/trezor/wire/__init__.py @@ -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 if __debug__: log.exception(__name__, exc) finally: diff --git a/core/translations/en.json b/core/translations/en.json index 02edf7b9feb..3037cba3d6b 100644 --- a/core/translations/en.json +++ b/core/translations/en.json @@ -499,6 +499,7 @@ "nem__under_namespace": "under namespace", "nem__unencrypted": "Unencrypted:", "nem__unknown_mosaic": "Unknown mosaic!", + "nostr__event_kind_template": "Event kind: {0}", "passphrase__access_wallet": "Access passphrase wallet?", "passphrase__always_on_device": "Always enter your passphrase on Trezor?", "passphrase__continue_with_empty_passphrase": "Continue with empty passphrase?", diff --git a/core/translations/order.json b/core/translations/order.json index 6fee8d6de78..059c779ac73 100644 --- a/core/translations/order.json +++ b/core/translations/order.json @@ -973,5 +973,6 @@ "971": "instructions__view_all_data", "972": "ethereum__interaction_contract", "973": "misc__enable_labeling", - "974": "ethereum__unknown_contract_address_short" + "974": "ethereum__unknown_contract_address_short", + "975": "nostr__event_kind_template" } diff --git a/core/translations/signatures.json b/core/translations/signatures.json index cdb37577ec2..6c51bea71be 100644 --- a/core/translations/signatures.json +++ b/core/translations/signatures.json @@ -1,8 +1,8 @@ { "current": { - "merkle_root": "53515eead12df806f139761eddc91f2aa2d3de3b9e0eb831552167ee25897f4a", - "datetime": "2024-12-16T11:26:54.578708", - "commit": "76301b1e97ea5ce0a2e17967f44a9db2a2e905e4" + "merkle_root": "5d84adedeb092763cb7dbef280fdfecb51b7741ef7684b8e8203d636ab8a780d", + "datetime": "2024-12-17T17:09:55.913209", + "commit": "625d552881b99a45569f214bf88c853cc54bbb64" }, "history": [ { diff --git a/legacy/firmware/protob/Makefile b/legacy/firmware/protob/Makefile index 48dbd5ddc7d..16c959d8348 100644 --- a/legacy/firmware/protob/Makefile +++ b/legacy/firmware/protob/Makefile @@ -12,7 +12,8 @@ SKIPPED_MESSAGES := Binance Cardano DebugMonero Eos Monero Ontology Ripple SdPro Solana StellarClaimClaimableBalanceOp \ ChangeLanguage TranslationDataRequest TranslationDataAck \ SetBrightness DebugLinkOptigaSetSecMax EntropyCheckReady EntropyCheckContinue \ - BenchmarkListNames BenchmarkRun BenchmarkNames BenchmarkResult + BenchmarkListNames BenchmarkRun BenchmarkNames BenchmarkResult \ + NostrGetPubkey NostrPubkey NostrSignEvent NostrEventSignature ifeq ($(BITCOIN_ONLY), 1) SKIPPED_MESSAGES += Ethereum NEM Stellar diff --git a/python/src/trezorlib/cli/nostr.py b/python/src/trezorlib/cli/nostr.py new file mode 100644 index 00000000000..9acfbf0b8ec --- /dev/null +++ b/python/src/trezorlib/cli/nostr.py @@ -0,0 +1,85 @@ +# This file is part of the Trezor project. +# +# Copyright (C) 2012-2025 SatoshiLabs and contributors +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 +# as published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the License along with this library. +# If not, see . + +import json +from typing import TYPE_CHECKING, Dict + +import click + +from .. import nostr, tools +from . import with_client + +if TYPE_CHECKING: + from ..client import TrezorClient + + +PATH_TEMPLATE = "m/44h/1237h/{}h/0/0" + + +@click.group(name="nostr") +def cli() -> None: + pass + + +@cli.command() +@click.option("-a", "--account", default=0, help="Account index") +@with_client +def get_pubkey( + client: "TrezorClient", + account: int, +) -> Dict[str, str]: + """Derive the pubkey from the seed.""" + + address_n = tools.parse_path(PATH_TEMPLATE.format(account)) + + res = nostr.get_pubkey( + client, + address_n, + ) + + return { + "pubkey": res.pubkey.hex(), + } + + +@cli.command() +@click.option("-a", "--account", default=0, help="Account index") +@click.argument("event") +@with_client +def sign_event( + client: "TrezorClient", + account: int, + event: str, +) -> Dict[str, str]: + """Sign an event using address of given path.""" + + event_json = json.loads(event) + + address_n = tools.parse_path(PATH_TEMPLATE.format(account)) + + res = nostr.sign_event( + client, + address_n, + event, + ) + + event_json["id"] = res.id.hex() + event_json["pubkey"] = res.pubkey.hex() + event_json["sig"] = res.signature.hex() + + return { + "signed_event": json.dumps(event_json), + } diff --git a/python/src/trezorlib/cli/trezorctl.py b/python/src/trezorlib/cli/trezorctl.py index 60f8e8d3092..d1f32a7c7ff 100755 --- a/python/src/trezorlib/cli/trezorctl.py +++ b/python/src/trezorlib/cli/trezorctl.py @@ -44,6 +44,7 @@ firmware, monero, nem, + nostr, ripple, settings, solana, @@ -409,6 +410,7 @@ def wait_for_emulator(obj: TrezorConnection, timeout: float) -> None: cli.add_command(fido.cli) cli.add_command(monero.cli) cli.add_command(nem.cli) +cli.add_command(nostr.cli) cli.add_command(ripple.cli) cli.add_command(settings.cli) cli.add_command(solana.cli) diff --git a/python/src/trezorlib/mapping.py b/python/src/trezorlib/mapping.py index d50324d5868..532277078ff 100644 --- a/python/src/trezorlib/mapping.py +++ b/python/src/trezorlib/mapping.py @@ -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( + f'Cannot encode class "{type(msg).__name__}" without wire type' + ) buf = io.BytesIO() protobuf.dump_message(buf, msg) diff --git a/python/src/trezorlib/messages.py b/python/src/trezorlib/messages.py index 06a36af68b3..dc51a63751b 100644 --- a/python/src/trezorlib/messages.py +++ b/python/src/trezorlib/messages.py @@ -644,6 +644,10 @@ class MessageType(IntEnum): SolanaAddress = 903 SolanaSignTx = 904 SolanaTxSignature = 905 + NostrGetPubkey = 2001 + NostrPubkey = 2002 + NostrSignEvent = 2003 + NostrEventSignature = 2004 BenchmarkListNames = 9100 BenchmarkNames = 9101 BenchmarkRun = 9102 @@ -6772,6 +6776,100 @@ def __init__( self.public_key = public_key +class NostrGetPubkey(protobuf.MessageType): + MESSAGE_WIRE_TYPE = 2001 + FIELDS = { + 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), + } + + def __init__( + self, + *, + address_n: Optional[Sequence["int"]] = None, + ) -> None: + self.address_n: Sequence["int"] = address_n if address_n is not None else [] + + +class NostrPubkey(protobuf.MessageType): + MESSAGE_WIRE_TYPE = 2002 + FIELDS = { + 1: protobuf.Field("pubkey", "bytes", repeated=False, required=True), + } + + def __init__( + self, + *, + pubkey: "bytes", + ) -> None: + self.pubkey = pubkey + + +class NostrTag(protobuf.MessageType): + MESSAGE_WIRE_TYPE = None + FIELDS = { + 1: protobuf.Field("key", "string", repeated=False, required=True), + 2: protobuf.Field("value", "string", repeated=False, required=False, default=None), + 3: protobuf.Field("extra", "string", repeated=True, required=False, default=None), + } + + def __init__( + self, + *, + key: "str", + extra: Optional[Sequence["str"]] = None, + value: Optional["str"] = None, + ) -> None: + self.extra: Sequence["str"] = extra if extra is not None else [] + self.key = key + self.value = value + + +class NostrSignEvent(protobuf.MessageType): + MESSAGE_WIRE_TYPE = 2003 + FIELDS = { + 1: protobuf.Field("address_n", "uint32", repeated=True, required=False, default=None), + 2: protobuf.Field("created_at", "uint32", repeated=False, required=True), + 3: protobuf.Field("kind", "uint32", repeated=False, required=True), + 4: protobuf.Field("tags", "NostrTag", repeated=True, required=False, default=None), + 5: protobuf.Field("content", "string", repeated=False, required=True), + } + + def __init__( + self, + *, + created_at: "int", + kind: "int", + content: "str", + address_n: Optional[Sequence["int"]] = None, + tags: Optional[Sequence["NostrTag"]] = None, + ) -> None: + self.address_n: Sequence["int"] = address_n if address_n is not None else [] + self.tags: Sequence["NostrTag"] = tags if tags is not None else [] + self.created_at = created_at + self.kind = kind + self.content = content + + +class NostrEventSignature(protobuf.MessageType): + MESSAGE_WIRE_TYPE = 2004 + FIELDS = { + 1: protobuf.Field("pubkey", "bytes", repeated=False, required=True), + 2: protobuf.Field("id", "bytes", repeated=False, required=True), + 3: protobuf.Field("signature", "bytes", repeated=False, required=True), + } + + def __init__( + self, + *, + pubkey: "bytes", + id: "bytes", + signature: "bytes", + ) -> None: + self.pubkey = pubkey + self.id = id + self.signature = signature + + class RippleGetAddress(protobuf.MessageType): MESSAGE_WIRE_TYPE = 400 FIELDS = { diff --git a/python/src/trezorlib/nostr.py b/python/src/trezorlib/nostr.py new file mode 100644 index 00000000000..9f6603ce57f --- /dev/null +++ b/python/src/trezorlib/nostr.py @@ -0,0 +1,62 @@ +# This file is part of the Trezor project. +# +# Copyright (C) 2012-2025 SatoshiLabs and contributors +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 +# as published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the License along with this library. +# If not, see . + + +import json +from typing import TYPE_CHECKING, AnyStr + +from . import messages +from .tools import expect + +if TYPE_CHECKING: + from .client import TrezorClient + from .protobuf import MessageType + from .tools import Address + + +@expect(messages.NostrPubkey) +def get_pubkey( + client: "TrezorClient", + n: "Address", +) -> "MessageType": + return client.call( + messages.NostrGetPubkey( + address_n=n, + ) + ) + + +@expect(messages.NostrEventSignature) +def sign_event( + client: "TrezorClient", + n: "Address", + event: AnyStr, +) -> "MessageType": + event_json = json.loads(event) + return client.call( + messages.NostrSignEvent( + address_n=n, + created_at=event_json["created_at"], + kind=event_json["kind"], + tags=[ + messages.NostrTag( + key=t[0], value=t[1] if len(t) > 1 else None, extra=t[2:] + ) + for t in event_json["tags"] + ], + content=event_json["content"], + ) + ) diff --git a/rust/trezor-client/scripts/build_messages b/rust/trezor-client/scripts/build_messages index 33d736fe3a1..9203783c5dc 100755 --- a/rust/trezor-client/scripts/build_messages +++ b/rust/trezor-client/scripts/build_messages @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -# Generates the `trezor_message_impl!` macro calls for the `src/messages/mod.rs` file. +# Generates the `trezor_message_impl!` macro calls for the `src/messages/generated.rs` file. from os import path @@ -24,6 +24,7 @@ FEATURES = { "EOS": "eos", "Monero": "monero", "NEM": "nem", + "Nostr": "nostr", "Ripple": "ripple", "Solana": "solana", "Stellar": "stellar", diff --git a/rust/trezor-client/src/messages/generated.rs b/rust/trezor-client/src/messages/generated.rs index 551a1e92e22..814125c4dce 100644 --- a/rust/trezor-client/src/messages/generated.rs +++ b/rust/trezor-client/src/messages/generated.rs @@ -237,6 +237,14 @@ trezor_message_impl! { NEMDecryptedMessage => MessageType_NEMDecryptedMessage, } +#[cfg(feature = "nostr")] +trezor_message_impl! { + NostrGetPubkey => MessageType_NostrGetPubkey, + NostrPubkey => MessageType_NostrPubkey, + NostrSignEvent => MessageType_NostrSignEvent, + NostrEventSignature => MessageType_NostrEventSignature, +} + #[cfg(feature = "ripple")] trezor_message_impl! { RippleGetAddress => MessageType_RippleGetAddress, diff --git a/rust/trezor-client/src/protos/generated/messages.rs b/rust/trezor-client/src/protos/generated/messages.rs index 0a265410a6c..e3797456aa5 100644 --- a/rust/trezor-client/src/protos/generated/messages.rs +++ b/rust/trezor-client/src/protos/generated/messages.rs @@ -514,6 +514,14 @@ pub enum MessageType { MessageType_SolanaSignTx = 904, // @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_SolanaTxSignature) MessageType_SolanaTxSignature = 905, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_NostrGetPubkey) + MessageType_NostrGetPubkey = 2001, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_NostrPubkey) + MessageType_NostrPubkey = 2002, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_NostrSignEvent) + MessageType_NostrSignEvent = 2003, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_NostrEventSignature) + MessageType_NostrEventSignature = 2004, // @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_BenchmarkListNames) MessageType_BenchmarkListNames = 9100, // @@protoc_insertion_point(enum_value:hw.trezor.messages.MessageType.MessageType_BenchmarkNames) @@ -776,6 +784,10 @@ impl ::protobuf::Enum for MessageType { 903 => ::std::option::Option::Some(MessageType::MessageType_SolanaAddress), 904 => ::std::option::Option::Some(MessageType::MessageType_SolanaSignTx), 905 => ::std::option::Option::Some(MessageType::MessageType_SolanaTxSignature), + 2001 => ::std::option::Option::Some(MessageType::MessageType_NostrGetPubkey), + 2002 => ::std::option::Option::Some(MessageType::MessageType_NostrPubkey), + 2003 => ::std::option::Option::Some(MessageType::MessageType_NostrSignEvent), + 2004 => ::std::option::Option::Some(MessageType::MessageType_NostrEventSignature), 9100 => ::std::option::Option::Some(MessageType::MessageType_BenchmarkListNames), 9101 => ::std::option::Option::Some(MessageType::MessageType_BenchmarkNames), 9102 => ::std::option::Option::Some(MessageType::MessageType_BenchmarkRun), @@ -1029,6 +1041,10 @@ impl ::protobuf::Enum for MessageType { "MessageType_SolanaAddress" => ::std::option::Option::Some(MessageType::MessageType_SolanaAddress), "MessageType_SolanaSignTx" => ::std::option::Option::Some(MessageType::MessageType_SolanaSignTx), "MessageType_SolanaTxSignature" => ::std::option::Option::Some(MessageType::MessageType_SolanaTxSignature), + "MessageType_NostrGetPubkey" => ::std::option::Option::Some(MessageType::MessageType_NostrGetPubkey), + "MessageType_NostrPubkey" => ::std::option::Option::Some(MessageType::MessageType_NostrPubkey), + "MessageType_NostrSignEvent" => ::std::option::Option::Some(MessageType::MessageType_NostrSignEvent), + "MessageType_NostrEventSignature" => ::std::option::Option::Some(MessageType::MessageType_NostrEventSignature), "MessageType_BenchmarkListNames" => ::std::option::Option::Some(MessageType::MessageType_BenchmarkListNames), "MessageType_BenchmarkNames" => ::std::option::Option::Some(MessageType::MessageType_BenchmarkNames), "MessageType_BenchmarkRun" => ::std::option::Option::Some(MessageType::MessageType_BenchmarkRun), @@ -1281,6 +1297,10 @@ impl ::protobuf::Enum for MessageType { MessageType::MessageType_SolanaAddress, MessageType::MessageType_SolanaSignTx, MessageType::MessageType_SolanaTxSignature, + MessageType::MessageType_NostrGetPubkey, + MessageType::MessageType_NostrPubkey, + MessageType::MessageType_NostrSignEvent, + MessageType::MessageType_NostrEventSignature, MessageType::MessageType_BenchmarkListNames, MessageType::MessageType_BenchmarkNames, MessageType::MessageType_BenchmarkRun, @@ -1539,10 +1559,14 @@ impl ::protobuf::EnumFull for MessageType { MessageType::MessageType_SolanaAddress => 240, MessageType::MessageType_SolanaSignTx => 241, MessageType::MessageType_SolanaTxSignature => 242, - MessageType::MessageType_BenchmarkListNames => 243, - MessageType::MessageType_BenchmarkNames => 244, - MessageType::MessageType_BenchmarkRun => 245, - MessageType::MessageType_BenchmarkResult => 246, + MessageType::MessageType_NostrGetPubkey => 243, + MessageType::MessageType_NostrPubkey => 244, + MessageType::MessageType_NostrSignEvent => 245, + MessageType::MessageType_NostrEventSignature => 246, + MessageType::MessageType_BenchmarkListNames => 247, + MessageType::MessageType_BenchmarkNames => 248, + MessageType::MessageType_BenchmarkRun => 249, + MessageType::MessageType_BenchmarkResult => 250, }; Self::enum_descriptor().value_by_index(index) } @@ -1561,7 +1585,7 @@ impl MessageType { } static file_descriptor_proto_data: &'static [u8] = b"\ - \n\x0emessages.proto\x12\x12hw.trezor.messages\x1a\roptions.proto*\xe8U\ + \n\x0emessages.proto\x12\x12hw.trezor.messages\x1a\roptions.proto*\x86W\ \n\x0bMessageType\x12(\n\x16MessageType_Initialize\x10\0\x1a\x0c\x80\xa6\ \x1d\x01\xb0\xb5\x18\x01\x90\xb5\x18\x01\x12\x1e\n\x10MessageType_Ping\ \x10\x01\x1a\x08\x80\xa6\x1d\x01\x90\xb5\x18\x01\x12%\n\x13MessageType_S\ @@ -1838,15 +1862,19 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \x18\x01\x12$\n\x19MessageType_SolanaAddress\x10\x87\x07\x1a\x04\x98\xb5\ \x18\x01\x12#\n\x18MessageType_SolanaSignTx\x10\x88\x07\x1a\x04\x90\xb5\ \x18\x01\x12(\n\x1dMessageType_SolanaTxSignature\x10\x89\x07\x1a\x04\x98\ - \xb5\x18\x01\x12)\n\x1eMessageType_BenchmarkListNames\x10\x8cG\x1a\x04\ - \x80\xa6\x1d\x01\x12%\n\x1aMessageType_BenchmarkNames\x10\x8dG\x1a\x04\ - \x80\xa6\x1d\x01\x12#\n\x18MessageType_BenchmarkRun\x10\x8eG\x1a\x04\x80\ - \xa6\x1d\x01\x12&\n\x1bMessageType_BenchmarkResult\x10\x8fG\x1a\x04\x80\ - \xa6\x1d\x01\x1a\x04\xc8\xf3\x18\x01\"\x04\x08Z\x10\\\"\x04\x08G\x10J\"\ - \x04\x08r\x10z\"\x06\x08\xdb\x01\x10\xdb\x01\"\x06\x08\xe0\x01\x10\xe0\ - \x01\"\x06\x08\xac\x02\x10\xb0\x02\"\x06\x08\xb5\x02\x10\xb8\x02\"\x06\ - \x08\xe8\x07\x10\xcb\x08B8\n#com.satoshilabs.trezor.lib.protobufB\rTrezo\ - rMessage\x80\xa6\x1d\x01\ + \xb5\x18\x01\x12%\n\x1aMessageType_NostrGetPubkey\x10\xd1\x0f\x1a\x04\ + \x90\xb5\x18\x01\x12\"\n\x17MessageType_NostrPubkey\x10\xd2\x0f\x1a\x04\ + \x98\xb5\x18\x01\x12%\n\x1aMessageType_NostrSignEvent\x10\xd3\x0f\x1a\ + \x04\x90\xb5\x18\x01\x12*\n\x1fMessageType_NostrEventSignature\x10\xd4\ + \x0f\x1a\x04\x98\xb5\x18\x01\x12)\n\x1eMessageType_BenchmarkListNames\ + \x10\x8cG\x1a\x04\x80\xa6\x1d\x01\x12%\n\x1aMessageType_BenchmarkNames\ + \x10\x8dG\x1a\x04\x80\xa6\x1d\x01\x12#\n\x18MessageType_BenchmarkRun\x10\ + \x8eG\x1a\x04\x80\xa6\x1d\x01\x12&\n\x1bMessageType_BenchmarkResult\x10\ + \x8fG\x1a\x04\x80\xa6\x1d\x01\x1a\x04\xc8\xf3\x18\x01\"\x04\x08Z\x10\\\"\ + \x04\x08G\x10J\"\x04\x08r\x10z\"\x06\x08\xdb\x01\x10\xdb\x01\"\x06\x08\ + \xe0\x01\x10\xe0\x01\"\x06\x08\xac\x02\x10\xb0\x02\"\x06\x08\xb5\x02\x10\ + \xb8\x02\"\x06\x08\xe8\x07\x10\xcb\x08B8\n#com.satoshilabs.trezor.lib.pr\ + otobufB\rTrezorMessage\x80\xa6\x1d\x01\ "; /// `FileDescriptorProto` object which was a source for this generated file diff --git a/rust/trezor-client/src/protos/generated/messages_nostr.rs b/rust/trezor-client/src/protos/generated/messages_nostr.rs new file mode 100644 index 00000000000..c26d124527f --- /dev/null +++ b/rust/trezor-client/src/protos/generated/messages_nostr.rs @@ -0,0 +1,1157 @@ +// This file is generated by rust-protobuf 3.3.0. Do not edit +// .proto file is parsed by protoc 3.19.6 +// @generated + +// https://github.com/rust-lang/rust-clippy/issues/702 +#![allow(unknown_lints)] +#![allow(clippy::all)] + +#![allow(unused_attributes)] +#![cfg_attr(rustfmt, rustfmt::skip)] + +#![allow(box_pointers)] +#![allow(dead_code)] +#![allow(missing_docs)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#![allow(trivial_casts)] +#![allow(unused_results)] +#![allow(unused_mut)] + +//! Generated file from `messages-nostr.proto` + +/// Generated files are compatible only with the same version +/// of protobuf runtime. +const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_3_3_0; + +// @@protoc_insertion_point(message:hw.trezor.messages.nostr.NostrGetPubkey) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct NostrGetPubkey { + // message fields + // @@protoc_insertion_point(field:hw.trezor.messages.nostr.NostrGetPubkey.address_n) + pub address_n: ::std::vec::Vec, + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.nostr.NostrGetPubkey.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a NostrGetPubkey { + fn default() -> &'a NostrGetPubkey { + ::default_instance() + } +} + +impl NostrGetPubkey { + pub fn new() -> NostrGetPubkey { + ::std::default::Default::default() + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(1); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( + "address_n", + |m: &NostrGetPubkey| { &m.address_n }, + |m: &mut NostrGetPubkey| { &mut m.address_n }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "NostrGetPubkey", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for NostrGetPubkey { + const NAME: &'static str = "NostrGetPubkey"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + is.read_repeated_packed_uint32_into(&mut self.address_n)?; + }, + 8 => { + self.address_n.push(is.read_uint32()?); + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + for value in &self.address_n { + my_size += ::protobuf::rt::uint32_size(1, *value); + }; + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + for v in &self.address_n { + os.write_uint32(1, *v)?; + }; + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> NostrGetPubkey { + NostrGetPubkey::new() + } + + fn clear(&mut self) { + self.address_n.clear(); + self.special_fields.clear(); + } + + fn default_instance() -> &'static NostrGetPubkey { + static instance: NostrGetPubkey = NostrGetPubkey { + address_n: ::std::vec::Vec::new(), + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for NostrGetPubkey { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("NostrGetPubkey").unwrap()).clone() + } +} + +impl ::std::fmt::Display for NostrGetPubkey { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for NostrGetPubkey { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:hw.trezor.messages.nostr.NostrPubkey) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct NostrPubkey { + // message fields + // @@protoc_insertion_point(field:hw.trezor.messages.nostr.NostrPubkey.pubkey) + pub pubkey: ::std::option::Option<::std::vec::Vec>, + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.nostr.NostrPubkey.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a NostrPubkey { + fn default() -> &'a NostrPubkey { + ::default_instance() + } +} + +impl NostrPubkey { + pub fn new() -> NostrPubkey { + ::std::default::Default::default() + } + + // required bytes pubkey = 1; + + pub fn pubkey(&self) -> &[u8] { + match self.pubkey.as_ref() { + Some(v) => v, + None => &[], + } + } + + pub fn clear_pubkey(&mut self) { + self.pubkey = ::std::option::Option::None; + } + + pub fn has_pubkey(&self) -> bool { + self.pubkey.is_some() + } + + // Param is passed by value, moved + pub fn set_pubkey(&mut self, v: ::std::vec::Vec) { + self.pubkey = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_pubkey(&mut self) -> &mut ::std::vec::Vec { + if self.pubkey.is_none() { + self.pubkey = ::std::option::Option::Some(::std::vec::Vec::new()); + } + self.pubkey.as_mut().unwrap() + } + + // Take field + pub fn take_pubkey(&mut self) -> ::std::vec::Vec { + self.pubkey.take().unwrap_or_else(|| ::std::vec::Vec::new()) + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(1); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "pubkey", + |m: &NostrPubkey| { &m.pubkey }, + |m: &mut NostrPubkey| { &mut m.pubkey }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "NostrPubkey", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for NostrPubkey { + const NAME: &'static str = "NostrPubkey"; + + fn is_initialized(&self) -> bool { + if self.pubkey.is_none() { + return false; + } + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.pubkey = ::std::option::Option::Some(is.read_bytes()?); + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if let Some(v) = self.pubkey.as_ref() { + my_size += ::protobuf::rt::bytes_size(1, &v); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if let Some(v) = self.pubkey.as_ref() { + os.write_bytes(1, v)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> NostrPubkey { + NostrPubkey::new() + } + + fn clear(&mut self) { + self.pubkey = ::std::option::Option::None; + self.special_fields.clear(); + } + + fn default_instance() -> &'static NostrPubkey { + static instance: NostrPubkey = NostrPubkey { + pubkey: ::std::option::Option::None, + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for NostrPubkey { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("NostrPubkey").unwrap()).clone() + } +} + +impl ::std::fmt::Display for NostrPubkey { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for NostrPubkey { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:hw.trezor.messages.nostr.NostrTag) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct NostrTag { + // message fields + // @@protoc_insertion_point(field:hw.trezor.messages.nostr.NostrTag.key) + pub key: ::std::option::Option<::std::string::String>, + // @@protoc_insertion_point(field:hw.trezor.messages.nostr.NostrTag.value) + pub value: ::std::option::Option<::std::string::String>, + // @@protoc_insertion_point(field:hw.trezor.messages.nostr.NostrTag.extra) + pub extra: ::std::vec::Vec<::std::string::String>, + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.nostr.NostrTag.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a NostrTag { + fn default() -> &'a NostrTag { + ::default_instance() + } +} + +impl NostrTag { + pub fn new() -> NostrTag { + ::std::default::Default::default() + } + + // required string key = 1; + + pub fn key(&self) -> &str { + match self.key.as_ref() { + Some(v) => v, + None => "", + } + } + + pub fn clear_key(&mut self) { + self.key = ::std::option::Option::None; + } + + pub fn has_key(&self) -> bool { + self.key.is_some() + } + + // Param is passed by value, moved + pub fn set_key(&mut self, v: ::std::string::String) { + self.key = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_key(&mut self) -> &mut ::std::string::String { + if self.key.is_none() { + self.key = ::std::option::Option::Some(::std::string::String::new()); + } + self.key.as_mut().unwrap() + } + + // Take field + pub fn take_key(&mut self) -> ::std::string::String { + self.key.take().unwrap_or_else(|| ::std::string::String::new()) + } + + // optional string value = 2; + + pub fn value(&self) -> &str { + match self.value.as_ref() { + Some(v) => v, + None => "", + } + } + + pub fn clear_value(&mut self) { + self.value = ::std::option::Option::None; + } + + pub fn has_value(&self) -> bool { + self.value.is_some() + } + + // Param is passed by value, moved + pub fn set_value(&mut self, v: ::std::string::String) { + self.value = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_value(&mut self) -> &mut ::std::string::String { + if self.value.is_none() { + self.value = ::std::option::Option::Some(::std::string::String::new()); + } + self.value.as_mut().unwrap() + } + + // Take field + pub fn take_value(&mut self) -> ::std::string::String { + self.value.take().unwrap_or_else(|| ::std::string::String::new()) + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(3); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "key", + |m: &NostrTag| { &m.key }, + |m: &mut NostrTag| { &mut m.key }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "value", + |m: &NostrTag| { &m.value }, + |m: &mut NostrTag| { &mut m.value }, + )); + fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( + "extra", + |m: &NostrTag| { &m.extra }, + |m: &mut NostrTag| { &mut m.extra }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "NostrTag", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for NostrTag { + const NAME: &'static str = "NostrTag"; + + fn is_initialized(&self) -> bool { + if self.key.is_none() { + return false; + } + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.key = ::std::option::Option::Some(is.read_string()?); + }, + 18 => { + self.value = ::std::option::Option::Some(is.read_string()?); + }, + 26 => { + self.extra.push(is.read_string()?); + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if let Some(v) = self.key.as_ref() { + my_size += ::protobuf::rt::string_size(1, &v); + } + if let Some(v) = self.value.as_ref() { + my_size += ::protobuf::rt::string_size(2, &v); + } + for value in &self.extra { + my_size += ::protobuf::rt::string_size(3, &value); + }; + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if let Some(v) = self.key.as_ref() { + os.write_string(1, v)?; + } + if let Some(v) = self.value.as_ref() { + os.write_string(2, v)?; + } + for v in &self.extra { + os.write_string(3, &v)?; + }; + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> NostrTag { + NostrTag::new() + } + + fn clear(&mut self) { + self.key = ::std::option::Option::None; + self.value = ::std::option::Option::None; + self.extra.clear(); + self.special_fields.clear(); + } + + fn default_instance() -> &'static NostrTag { + static instance: NostrTag = NostrTag { + key: ::std::option::Option::None, + value: ::std::option::Option::None, + extra: ::std::vec::Vec::new(), + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for NostrTag { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("NostrTag").unwrap()).clone() + } +} + +impl ::std::fmt::Display for NostrTag { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for NostrTag { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:hw.trezor.messages.nostr.NostrSignEvent) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct NostrSignEvent { + // message fields + // @@protoc_insertion_point(field:hw.trezor.messages.nostr.NostrSignEvent.address_n) + pub address_n: ::std::vec::Vec, + // @@protoc_insertion_point(field:hw.trezor.messages.nostr.NostrSignEvent.created_at) + pub created_at: ::std::option::Option, + // @@protoc_insertion_point(field:hw.trezor.messages.nostr.NostrSignEvent.kind) + pub kind: ::std::option::Option, + // @@protoc_insertion_point(field:hw.trezor.messages.nostr.NostrSignEvent.tags) + pub tags: ::std::vec::Vec, + // @@protoc_insertion_point(field:hw.trezor.messages.nostr.NostrSignEvent.content) + pub content: ::std::option::Option<::std::string::String>, + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.nostr.NostrSignEvent.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a NostrSignEvent { + fn default() -> &'a NostrSignEvent { + ::default_instance() + } +} + +impl NostrSignEvent { + pub fn new() -> NostrSignEvent { + ::std::default::Default::default() + } + + // required uint32 created_at = 2; + + pub fn created_at(&self) -> u32 { + self.created_at.unwrap_or(0) + } + + pub fn clear_created_at(&mut self) { + self.created_at = ::std::option::Option::None; + } + + pub fn has_created_at(&self) -> bool { + self.created_at.is_some() + } + + // Param is passed by value, moved + pub fn set_created_at(&mut self, v: u32) { + self.created_at = ::std::option::Option::Some(v); + } + + // required uint32 kind = 3; + + pub fn kind(&self) -> u32 { + self.kind.unwrap_or(0) + } + + pub fn clear_kind(&mut self) { + self.kind = ::std::option::Option::None; + } + + pub fn has_kind(&self) -> bool { + self.kind.is_some() + } + + // Param is passed by value, moved + pub fn set_kind(&mut self, v: u32) { + self.kind = ::std::option::Option::Some(v); + } + + // required string content = 5; + + pub fn content(&self) -> &str { + match self.content.as_ref() { + Some(v) => v, + None => "", + } + } + + pub fn clear_content(&mut self) { + self.content = ::std::option::Option::None; + } + + pub fn has_content(&self) -> bool { + self.content.is_some() + } + + // Param is passed by value, moved + pub fn set_content(&mut self, v: ::std::string::String) { + self.content = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_content(&mut self) -> &mut ::std::string::String { + if self.content.is_none() { + self.content = ::std::option::Option::Some(::std::string::String::new()); + } + self.content.as_mut().unwrap() + } + + // Take field + pub fn take_content(&mut self) -> ::std::string::String { + self.content.take().unwrap_or_else(|| ::std::string::String::new()) + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(5); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( + "address_n", + |m: &NostrSignEvent| { &m.address_n }, + |m: &mut NostrSignEvent| { &mut m.address_n }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "created_at", + |m: &NostrSignEvent| { &m.created_at }, + |m: &mut NostrSignEvent| { &mut m.created_at }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "kind", + |m: &NostrSignEvent| { &m.kind }, + |m: &mut NostrSignEvent| { &mut m.kind }, + )); + fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( + "tags", + |m: &NostrSignEvent| { &m.tags }, + |m: &mut NostrSignEvent| { &mut m.tags }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "content", + |m: &NostrSignEvent| { &m.content }, + |m: &mut NostrSignEvent| { &mut m.content }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "NostrSignEvent", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for NostrSignEvent { + const NAME: &'static str = "NostrSignEvent"; + + fn is_initialized(&self) -> bool { + if self.created_at.is_none() { + return false; + } + if self.kind.is_none() { + return false; + } + if self.content.is_none() { + return false; + } + for v in &self.tags { + if !v.is_initialized() { + return false; + } + }; + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + is.read_repeated_packed_uint32_into(&mut self.address_n)?; + }, + 8 => { + self.address_n.push(is.read_uint32()?); + }, + 16 => { + self.created_at = ::std::option::Option::Some(is.read_uint32()?); + }, + 24 => { + self.kind = ::std::option::Option::Some(is.read_uint32()?); + }, + 34 => { + self.tags.push(is.read_message()?); + }, + 42 => { + self.content = ::std::option::Option::Some(is.read_string()?); + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + for value in &self.address_n { + my_size += ::protobuf::rt::uint32_size(1, *value); + }; + if let Some(v) = self.created_at { + my_size += ::protobuf::rt::uint32_size(2, v); + } + if let Some(v) = self.kind { + my_size += ::protobuf::rt::uint32_size(3, v); + } + for value in &self.tags { + let len = value.compute_size(); + my_size += 1 + ::protobuf::rt::compute_raw_varint64_size(len) + len; + }; + if let Some(v) = self.content.as_ref() { + my_size += ::protobuf::rt::string_size(5, &v); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + for v in &self.address_n { + os.write_uint32(1, *v)?; + }; + if let Some(v) = self.created_at { + os.write_uint32(2, v)?; + } + if let Some(v) = self.kind { + os.write_uint32(3, v)?; + } + for v in &self.tags { + ::protobuf::rt::write_message_field_with_cached_size(4, v, os)?; + }; + if let Some(v) = self.content.as_ref() { + os.write_string(5, v)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> NostrSignEvent { + NostrSignEvent::new() + } + + fn clear(&mut self) { + self.address_n.clear(); + self.created_at = ::std::option::Option::None; + self.kind = ::std::option::Option::None; + self.tags.clear(); + self.content = ::std::option::Option::None; + self.special_fields.clear(); + } + + fn default_instance() -> &'static NostrSignEvent { + static instance: NostrSignEvent = NostrSignEvent { + address_n: ::std::vec::Vec::new(), + created_at: ::std::option::Option::None, + kind: ::std::option::Option::None, + tags: ::std::vec::Vec::new(), + content: ::std::option::Option::None, + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for NostrSignEvent { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("NostrSignEvent").unwrap()).clone() + } +} + +impl ::std::fmt::Display for NostrSignEvent { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for NostrSignEvent { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:hw.trezor.messages.nostr.NostrEventSignature) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct NostrEventSignature { + // message fields + // @@protoc_insertion_point(field:hw.trezor.messages.nostr.NostrEventSignature.pubkey) + pub pubkey: ::std::option::Option<::std::vec::Vec>, + // @@protoc_insertion_point(field:hw.trezor.messages.nostr.NostrEventSignature.id) + pub id: ::std::option::Option<::std::vec::Vec>, + // @@protoc_insertion_point(field:hw.trezor.messages.nostr.NostrEventSignature.signature) + pub signature: ::std::option::Option<::std::vec::Vec>, + // special fields + // @@protoc_insertion_point(special_field:hw.trezor.messages.nostr.NostrEventSignature.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a NostrEventSignature { + fn default() -> &'a NostrEventSignature { + ::default_instance() + } +} + +impl NostrEventSignature { + pub fn new() -> NostrEventSignature { + ::std::default::Default::default() + } + + // required bytes pubkey = 1; + + pub fn pubkey(&self) -> &[u8] { + match self.pubkey.as_ref() { + Some(v) => v, + None => &[], + } + } + + pub fn clear_pubkey(&mut self) { + self.pubkey = ::std::option::Option::None; + } + + pub fn has_pubkey(&self) -> bool { + self.pubkey.is_some() + } + + // Param is passed by value, moved + pub fn set_pubkey(&mut self, v: ::std::vec::Vec) { + self.pubkey = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_pubkey(&mut self) -> &mut ::std::vec::Vec { + if self.pubkey.is_none() { + self.pubkey = ::std::option::Option::Some(::std::vec::Vec::new()); + } + self.pubkey.as_mut().unwrap() + } + + // Take field + pub fn take_pubkey(&mut self) -> ::std::vec::Vec { + self.pubkey.take().unwrap_or_else(|| ::std::vec::Vec::new()) + } + + // required bytes id = 2; + + pub fn id(&self) -> &[u8] { + match self.id.as_ref() { + Some(v) => v, + None => &[], + } + } + + pub fn clear_id(&mut self) { + self.id = ::std::option::Option::None; + } + + pub fn has_id(&self) -> bool { + self.id.is_some() + } + + // Param is passed by value, moved + pub fn set_id(&mut self, v: ::std::vec::Vec) { + self.id = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_id(&mut self) -> &mut ::std::vec::Vec { + if self.id.is_none() { + self.id = ::std::option::Option::Some(::std::vec::Vec::new()); + } + self.id.as_mut().unwrap() + } + + // Take field + pub fn take_id(&mut self) -> ::std::vec::Vec { + self.id.take().unwrap_or_else(|| ::std::vec::Vec::new()) + } + + // required bytes signature = 3; + + pub fn signature(&self) -> &[u8] { + match self.signature.as_ref() { + Some(v) => v, + None => &[], + } + } + + pub fn clear_signature(&mut self) { + self.signature = ::std::option::Option::None; + } + + pub fn has_signature(&self) -> bool { + self.signature.is_some() + } + + // Param is passed by value, moved + pub fn set_signature(&mut self, v: ::std::vec::Vec) { + self.signature = ::std::option::Option::Some(v); + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_signature(&mut self) -> &mut ::std::vec::Vec { + if self.signature.is_none() { + self.signature = ::std::option::Option::Some(::std::vec::Vec::new()); + } + self.signature.as_mut().unwrap() + } + + // Take field + pub fn take_signature(&mut self) -> ::std::vec::Vec { + self.signature.take().unwrap_or_else(|| ::std::vec::Vec::new()) + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(3); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "pubkey", + |m: &NostrEventSignature| { &m.pubkey }, + |m: &mut NostrEventSignature| { &mut m.pubkey }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "id", + |m: &NostrEventSignature| { &m.id }, + |m: &mut NostrEventSignature| { &mut m.id }, + )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "signature", + |m: &NostrEventSignature| { &m.signature }, + |m: &mut NostrEventSignature| { &mut m.signature }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "NostrEventSignature", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for NostrEventSignature { + const NAME: &'static str = "NostrEventSignature"; + + fn is_initialized(&self) -> bool { + if self.pubkey.is_none() { + return false; + } + if self.id.is_none() { + return false; + } + if self.signature.is_none() { + return false; + } + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.pubkey = ::std::option::Option::Some(is.read_bytes()?); + }, + 18 => { + self.id = ::std::option::Option::Some(is.read_bytes()?); + }, + 26 => { + self.signature = ::std::option::Option::Some(is.read_bytes()?); + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if let Some(v) = self.pubkey.as_ref() { + my_size += ::protobuf::rt::bytes_size(1, &v); + } + if let Some(v) = self.id.as_ref() { + my_size += ::protobuf::rt::bytes_size(2, &v); + } + if let Some(v) = self.signature.as_ref() { + my_size += ::protobuf::rt::bytes_size(3, &v); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if let Some(v) = self.pubkey.as_ref() { + os.write_bytes(1, v)?; + } + if let Some(v) = self.id.as_ref() { + os.write_bytes(2, v)?; + } + if let Some(v) = self.signature.as_ref() { + os.write_bytes(3, v)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> NostrEventSignature { + NostrEventSignature::new() + } + + fn clear(&mut self) { + self.pubkey = ::std::option::Option::None; + self.id = ::std::option::Option::None; + self.signature = ::std::option::Option::None; + self.special_fields.clear(); + } + + fn default_instance() -> &'static NostrEventSignature { + static instance: NostrEventSignature = NostrEventSignature { + pubkey: ::std::option::Option::None, + id: ::std::option::Option::None, + signature: ::std::option::Option::None, + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for NostrEventSignature { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("NostrEventSignature").unwrap()).clone() + } +} + +impl ::std::fmt::Display for NostrEventSignature { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for NostrEventSignature { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +static file_descriptor_proto_data: &'static [u8] = b"\ + \n\x14messages-nostr.proto\x12\x18hw.trezor.messages.nostr\x1a\roptions.\ + proto\"-\n\x0eNostrGetPubkey\x12\x1b\n\taddress_n\x18\x01\x20\x03(\rR\ + \x08addressN\"%\n\x0bNostrPubkey\x12\x16\n\x06pubkey\x18\x01\x20\x02(\ + \x0cR\x06pubkey\"H\n\x08NostrTag\x12\x10\n\x03key\x18\x01\x20\x02(\tR\ + \x03key\x12\x14\n\x05value\x18\x02\x20\x01(\tR\x05value\x12\x14\n\x05ext\ + ra\x18\x03\x20\x03(\tR\x05extra\"\xb2\x01\n\x0eNostrSignEvent\x12\x1b\n\ + \taddress_n\x18\x01\x20\x03(\rR\x08addressN\x12\x1d\n\ncreated_at\x18\ + \x02\x20\x02(\rR\tcreatedAt\x12\x12\n\x04kind\x18\x03\x20\x02(\rR\x04kin\ + d\x126\n\x04tags\x18\x04\x20\x03(\x0b2\".hw.trezor.messages.nostr.NostrT\ + agR\x04tags\x12\x18\n\x07content\x18\x05\x20\x02(\tR\x07content\"[\n\x13\ + NostrEventSignature\x12\x16\n\x06pubkey\x18\x01\x20\x02(\x0cR\x06pubkey\ + \x12\x0e\n\x02id\x18\x02\x20\x02(\x0cR\x02id\x12\x1c\n\tsignature\x18\ + \x03\x20\x02(\x0cR\tsignatureB9\n#com.satoshilabs.trezor.lib.protobufB\ + \x12TrezorMessageNostr\ +"; + +/// `FileDescriptorProto` object which was a source for this generated file +fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto { + static file_descriptor_proto_lazy: ::protobuf::rt::Lazy<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::Lazy::new(); + file_descriptor_proto_lazy.get(|| { + ::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap() + }) +} + +/// `FileDescriptor` object which allows dynamic access to files +pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor { + static generated_file_descriptor_lazy: ::protobuf::rt::Lazy<::protobuf::reflect::GeneratedFileDescriptor> = ::protobuf::rt::Lazy::new(); + static file_descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::FileDescriptor> = ::protobuf::rt::Lazy::new(); + file_descriptor.get(|| { + let generated_file_descriptor = generated_file_descriptor_lazy.get(|| { + let mut deps = ::std::vec::Vec::with_capacity(1); + deps.push(super::options::file_descriptor().clone()); + let mut messages = ::std::vec::Vec::with_capacity(5); + messages.push(NostrGetPubkey::generated_message_descriptor_data()); + messages.push(NostrPubkey::generated_message_descriptor_data()); + messages.push(NostrTag::generated_message_descriptor_data()); + messages.push(NostrSignEvent::generated_message_descriptor_data()); + messages.push(NostrEventSignature::generated_message_descriptor_data()); + let mut enums = ::std::vec::Vec::with_capacity(0); + ::protobuf::reflect::GeneratedFileDescriptor::new_generated( + file_descriptor_proto(), + deps, + messages, + enums, + ) + }); + ::protobuf::reflect::FileDescriptor::new_generated_2(generated_file_descriptor) + }) +} diff --git a/rust/trezor-client/src/protos/mod.rs b/rust/trezor-client/src/protos/mod.rs index 7dd8483f9c4..802423dab51 100644 --- a/rust/trezor-client/src/protos/mod.rs +++ b/rust/trezor-client/src/protos/mod.rs @@ -33,6 +33,7 @@ mod generated { "eos" => messages_eos "monero" => messages_monero "nem" => messages_nem + "nostr" => messages_nostr "ripple" => messages_ripple "solana" => messages_solana "stellar" => messages_stellar diff --git a/tests/device_tests/nostr/__init__.py b/tests/device_tests/nostr/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/device_tests/nostr/test_nostr.py b/tests/device_tests/nostr/test_nostr.py new file mode 100644 index 00000000000..f7c880f3988 --- /dev/null +++ b/tests/device_tests/nostr/test_nostr.py @@ -0,0 +1,117 @@ +# This file is part of the Trezor project. +# +# Copyright (C) 2012-2025 SatoshiLabs and contributors +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 +# as published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the License along with this library. +# If not, see . + +import json +import time +from hashlib import sha256 + +import pytest +from ecdsa import SECP256k1, VerifyingKey +from six import b + +from trezorlib import nostr +from trezorlib.debuglink import TrezorClientDebugLink as Client +from trezorlib.tools import parse_path + +pytestmark = [pytest.mark.altcoin, pytest.mark.models("core")] + +# test data from NIP-06: https://github.com/nostr-protocol/nips/blob/master/06.md + +LEAD_MONKEY_MNEMONIC = ( + "leader monkey parrot ring guide accident before fence cannon height naive bean" +) +LEAD_MONKEY_PK = "17162c921dc4d2518f9a101db33695df1afb56ab82f5ff3e5da6eec3ca5cd917" + +WHAT_BLEAK_MNEMONIC = "what bleak badge arrange retreat wolf trade produce cricket blur garlic valid proud rude strong choose busy staff weather area salt hollow arm fade" +WHAT_BLEAK_PK = "d41b22899549e1f3d335a31002cfd382174006e166d3e658e3a5eecdb6463573" + + +@pytest.mark.setup_client(mnemonic=LEAD_MONKEY_MNEMONIC) +def test_sign_event_lead_monkey(client: Client): + _test_sign_event(client, LEAD_MONKEY_PK) + + +@pytest.mark.setup_client(mnemonic=WHAT_BLEAK_MNEMONIC) +def test_sign_event_what_bleak(client: Client): + _test_sign_event(client, WHAT_BLEAK_PK) + + +@pytest.mark.setup_client(mnemonic=LEAD_MONKEY_MNEMONIC) +def test_get_pubkey_lead_monkey(client: Client): + _test_get_pubkey(client, LEAD_MONKEY_PK) + + +@pytest.mark.setup_client(mnemonic=WHAT_BLEAK_MNEMONIC) +def test_get_pubkey_what_bleak(client: Client): + _test_get_pubkey(client, WHAT_BLEAK_PK) + + +def _test_get_pubkey(client, expected_pk): + response = nostr.get_pubkey( + client, + n=parse_path("m/44h/1237h/0h/0/0"), + ) + + assert response.pubkey == bytes.fromhex(expected_pk) + + +def _test_sign_event(client, expected_pk): + created_at = int(time.time()) + kind = 1 + tags = [ + [ + "e", + "5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36", + "wss://nostr.example.com", + ], + ["p", "f7234bd4c1394dda46d09f35bd384dd30cc552ad5541990f98844fb06676e9ca"], + [ + "a", + "30023:f7234bd4c1394dda46d09f35bd384dd30cc552ad5541990f98844fb06676e9ca:abcd", + "wss://nostr.example.com", + ], + ["alt", "reply"], + ] + content = "Hello, world!" + + response = nostr.sign_event( + client, + event=json.dumps( + {"created_at": created_at, "kind": kind, "tags": tags, "content": content} + ), + n=parse_path("m/44h/1237h/0h/0/0"), + ) + + assert response.pubkey == bytes.fromhex(expected_pk) + + expected_id = sha256( + json.dumps( + [0, expected_pk, created_at, kind, tags, content], separators=(",", ":") + ).encode() + ).digest() + + assert response.id == expected_id + + vk = VerifyingKey.from_string( + b("\x03") + bytes.fromhex(expected_pk), + curve=SECP256k1, + # this is a pretty silly way to tell VerifyingKey + # that we do not want the message to be hashed + # when verifying the signature! + hashfunc=lambda x: type("h", (), {"digest": lambda: x}), + ) + + assert vk.verify(response.signature, response.id) diff --git a/tests/ui_tests/fixtures.json b/tests/ui_tests/fixtures.json index b5789dbbbd5..12ebe901e08 100644 --- a/tests/ui_tests/fixtures.json +++ b/tests/ui_tests/fixtures.json @@ -4998,6 +4998,10 @@ "T2T1_en_nem-test_signtx_transfers.py::test_nem_signtx_simple[True]": "513c1123880208703c94bcb1ce527d1f584f77b6f5144344bbc3b426ad657d70", "T2T1_en_nem-test_signtx_transfers.py::test_nem_signtx_unknown_mosaic": "63ede54ab17f687c9d1e88ebeea0a87059862fab22220d882718c77338fa2aee", "T2T1_en_nem-test_signtx_transfers.py::test_nem_signtx_xem_as_mosaic": "bf860cbea5a20cf275643c4b640cade9afb902a757f88d7b701f82e5dd025b74", +"T2T1_en_nostr-test_nostr.py::test_get_pubkey_lead_monkey": "8b1ccc0dbd6e6e3d02a896650ab90dd332ba4edbbcc4095e0fbb6a96e5256f75", +"T2T1_en_nostr-test_nostr.py::test_get_pubkey_what_bleak": "8b1ccc0dbd6e6e3d02a896650ab90dd332ba4edbbcc4095e0fbb6a96e5256f75", +"T2T1_en_nostr-test_nostr.py::test_sign_event_lead_monkey": "d164c81dd25147f83b67cf592414759f25ef013dc2f93de3674f39c00625c895", +"T2T1_en_nostr-test_nostr.py::test_sign_event_what_bleak": "d164c81dd25147f83b67cf592414759f25ef013dc2f93de3674f39c00625c895", "T2T1_en_reset_recovery-test_recovery_bip39_dryrun.py::test_bad_parameters[label-test]": "8b1ccc0dbd6e6e3d02a896650ab90dd332ba4edbbcc4095e0fbb6a96e5256f75", "T2T1_en_reset_recovery-test_recovery_bip39_dryrun.py::test_bad_parameters[passphrase_protection-True]": "8b1ccc0dbd6e6e3d02a896650ab90dd332ba4edbbcc4095e0fbb6a96e5256f75", "T2T1_en_reset_recovery-test_recovery_bip39_dryrun.py::test_bad_parameters[pin_protection-True]": "8b1ccc0dbd6e6e3d02a896650ab90dd332ba4edbbcc4095e0fbb6a96e5256f75", @@ -13775,6 +13779,10 @@ "T3B1_en_monero-test_getaddress.py::test_monero_getaddress_chunkify_details[m-44h-128h-1h-44iAazhoAk-005b0fdf": "c2e678b794aee3668ad09dec649dca3628f20c8a237c5eee3ee276f7ccef0758", "T3B1_en_monero-test_getaddress.py::test_monero_getaddress_chunkify_details[m-44h-128h-2h-47ejhmbZ4w-df40ccfd": "0fac71315cf53c369b508167926a69c6813c29447f435fd78394ede98c44b0c1", "T3B1_en_monero-test_getwatchkey.py::test_monero_getwatchkey": "e9f6381bfc182df51a644dbd3ed7e42ee983871523d853f9a0145d062f80b1f5", +"T3B1_en_nostr-test_nostr.py::test_get_pubkey_lead_monkey": "1477d62e338f4d7c1bfac2fc5d2fc231218da5768666c11482dc1f83229506f3", +"T3B1_en_nostr-test_nostr.py::test_get_pubkey_what_bleak": "1477d62e338f4d7c1bfac2fc5d2fc231218da5768666c11482dc1f83229506f3", +"T3B1_en_nostr-test_nostr.py::test_sign_event_lead_monkey": "cd7c8ef54329c264df421645dd78e46931e5521c10f4969197ecbab6a1d512a3", +"T3B1_en_nostr-test_nostr.py::test_sign_event_what_bleak": "cd7c8ef54329c264df421645dd78e46931e5521c10f4969197ecbab6a1d512a3", "T3B1_en_reset_recovery-test_recovery_bip39_dryrun.py::test_bad_parameters[label-test]": "1477d62e338f4d7c1bfac2fc5d2fc231218da5768666c11482dc1f83229506f3", "T3B1_en_reset_recovery-test_recovery_bip39_dryrun.py::test_bad_parameters[passphrase_protection-True]": "1477d62e338f4d7c1bfac2fc5d2fc231218da5768666c11482dc1f83229506f3", "T3B1_en_reset_recovery-test_recovery_bip39_dryrun.py::test_bad_parameters[pin_protection-True]": "1477d62e338f4d7c1bfac2fc5d2fc231218da5768666c11482dc1f83229506f3", @@ -22387,6 +22395,10 @@ "T3T1_en_monero-test_getaddress.py::test_monero_getaddress_chunkify_details[m-44h-128h-1h-44iAazhoAk-005b0fdf": "3da835571e0ab6e41a7bf923ca53abe5062964e4808d9f4d9c5030056bb7ca6a", "T3T1_en_monero-test_getaddress.py::test_monero_getaddress_chunkify_details[m-44h-128h-2h-47ejhmbZ4w-df40ccfd": "cd0b0f2b85d35442787d75864b4eeee6409ab3e29b4ecd1363a6acfac8ffca37", "T3T1_en_monero-test_getwatchkey.py::test_monero_getwatchkey": "f8d7d165b3f70cf5c527ba0e81893588b7d1bca1b88787cf41aa590592d95d36", +"T3T1_en_nostr-test_nostr.py::test_get_pubkey_lead_monkey": "3c5fb7d6110128ed52024a6b92654210b7acad6fe08b568d5238bfceb257a524", +"T3T1_en_nostr-test_nostr.py::test_get_pubkey_what_bleak": "3c5fb7d6110128ed52024a6b92654210b7acad6fe08b568d5238bfceb257a524", +"T3T1_en_nostr-test_nostr.py::test_sign_event_lead_monkey": "332aaa4e66fb6acde0c54ce4f3c7a357fd6cc6967a44b07fec1f2de21521d8ff", +"T3T1_en_nostr-test_nostr.py::test_sign_event_what_bleak": "332aaa4e66fb6acde0c54ce4f3c7a357fd6cc6967a44b07fec1f2de21521d8ff", "T3T1_en_reset_recovery-test_recovery_bip39_dryrun.py::test_bad_parameters[label-test]": "3c5fb7d6110128ed52024a6b92654210b7acad6fe08b568d5238bfceb257a524", "T3T1_en_reset_recovery-test_recovery_bip39_dryrun.py::test_bad_parameters[passphrase_protection-True]": "3c5fb7d6110128ed52024a6b92654210b7acad6fe08b568d5238bfceb257a524", "T3T1_en_reset_recovery-test_recovery_bip39_dryrun.py::test_bad_parameters[pin_protection-True]": "3c5fb7d6110128ed52024a6b92654210b7acad6fe08b568d5238bfceb257a524",