1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-02-27 14:52:10 +00:00

feat(core): introduce Nostr

[no changelog]
This commit is contained in:
Ioan Bizău 2024-10-07 13:23:49 +02:00
parent 323107e6a8
commit f013340a25
26 changed files with 1839 additions and 16 deletions

View File

@ -0,0 +1,64 @@
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";
/**
* 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
}

View File

@ -324,6 +324,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];

View File

@ -0,0 +1 @@
Add Nostr support (in debug mode only!).

View File

@ -662,6 +662,10 @@ if FROZEN:
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/nem/*/*.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/enums/NEM*.py'))
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'))

View File

@ -721,6 +721,10 @@ if FROZEN:
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/nem/*/*.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/enums/NEM*.py'))
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'))

View File

@ -15,6 +15,7 @@ ALTCOIN_PREFIXES = (
"fido",
"monero",
"nem",
"nostr",
"ripple",
"solana",
"stellar",

View File

@ -415,6 +415,12 @@ apps.misc.get_firmware_hash
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

View File

@ -0,0 +1,9 @@
from apps.common.paths import PATTERN_BIP44
CURVE = "secp256k1"
SLIP44_ID = 1237
# Note: we are not satisfied with using this path even though it is defined in NIP-06.
# See this issue for details: https://github.com/nostr-protocol/nips/issues/1774
# TODO: we need to create a new NIP using a different derivation path and use that here!
PATTERN = PATTERN_BIP44

View File

@ -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)

View File

@ -0,0 +1,48 @@
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.crypto.curve import secp256k1
from trezor.crypto.hashlib import sha256
from trezor.messages import NostrEventSignature
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:]
# 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,
)

View File

@ -106,6 +106,12 @@ def _find_message_handler_module(msg_type: int) -> str:
if msg_type == MessageType.GetFirmwareHash:
return "apps.misc.get_firmware_hash"
# 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"

View File

@ -250,3 +250,7 @@ if not utils.BITCOIN_ONLY:
SolanaAddress = 903
SolanaSignTx = 904
SolanaTxSignature = 905
NostrGetPubkey = 2001
NostrPubkey = 2002
NostrSignEvent = 2003
NostrEventSignature = 2004

View File

@ -591,6 +591,10 @@ if TYPE_CHECKING:
SolanaAddress = 903
SolanaSignTx = 904
SolanaTxSignature = 905
NostrGetPubkey = 2001
NostrPubkey = 2002
NostrSignEvent = 2003
NostrEventSignature = 2004
BenchmarkListNames = 9100
BenchmarkNames = 9101
BenchmarkRun = 9102

View File

@ -5216,6 +5216,92 @@ if TYPE_CHECKING:
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"

View File

@ -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

View File

@ -0,0 +1,93 @@
# 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 <https://www.gnu.org/licenses/lgpl-3.0.html>.
from __future__ import annotations
import json
import typing as t
import click
from .. import messages, nostr, tools
from . import with_client
if t.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,
) -> str:
"""Return the pubkey derived by the given path."""
address_n = tools.parse_path(PATH_TEMPLATE.format(account))
return nostr.get_pubkey(
client,
address_n,
).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 the key derived by the given path."""
event_json = json.loads(event)
address_n = tools.parse_path(PATH_TEMPLATE.format(account))
res = nostr.sign_event(
client,
messages.NostrSignEvent(
address_n=address_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"],
),
)
event_json["id"] = res.id.hex()
event_json["pubkey"] = res.pubkey.hex()
event_json["sig"] = res.signature.hex()
return {
"signed_event": event_json,
}

View File

@ -44,6 +44,7 @@ from . import (
firmware,
monero,
nem,
nostr,
ripple,
settings,
solana,
@ -409,6 +410,7 @@ cli.add_command(ethereum.cli)
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)

View File

@ -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
@ -6775,6 +6779,100 @@ class NEMCosignatoryModification(protobuf.MessageType):
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 = {

View File

@ -0,0 +1,40 @@
# 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 <https://www.gnu.org/licenses/lgpl-3.0.html>.
from typing import TYPE_CHECKING
from . import messages
if TYPE_CHECKING:
from .client import TrezorClient
from .tools import Address
def get_pubkey(client: "TrezorClient", n: "Address") -> bytes:
return client.call(
messages.NostrGetPubkey(
address_n=n,
),
expect=messages.NostrPubkey,
).pubkey
def sign_event(
client: "TrezorClient",
sign_event: messages.NostrSignEvent,
) -> messages.NostrEventSignature:
return client.call(sign_event, expect=messages.NostrEventSignature)

View File

@ -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",

View File

@ -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,

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

View File

@ -0,0 +1,129 @@
# 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 <https://www.gnu.org/licenses/lgpl-3.0.html>.
import json
from hashlib import sha256
import pytest
from ecdsa import SECP256k1, VerifyingKey
from six import b
from trezorlib import messages, nostr
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_PUBKEY_HEX = (
"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_PUBKEY_HEX = (
"d41b22899549e1f3d335a31002cfd382174006e166d3e658e3a5eecdb6463573"
)
pytestmark_lead_monkey = pytest.mark.setup_client(mnemonic=LEAD_MONKEY_MNEMONIC)
pytestmark_what_bleak = pytest.mark.setup_client(mnemonic=WHAT_BLEAK_MNEMONIC)
VECTORS = [
pytest.param(LEAD_MONKEY_PUBKEY_HEX, marks=pytestmark_lead_monkey),
pytest.param(WHAT_BLEAK_PUBKEY_HEX, marks=pytestmark_what_bleak),
]
TEST_EVENT = {
"created_at": 1737396950,
"kind": 1,
"tags": [
[
"e",
"5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36",
"wss://nostr.example.com",
],
["p", "f7234bd4c1394dda46d09f35bd384dd30cc552ad5541990f98844fb06676e9ca"],
[
"a",
"30023:f7234bd4c1394dda46d09f35bd384dd30cc552ad5541990f98844fb06676e9ca:abcd",
"wss://nostr.example.com",
],
["alt", "reply"],
],
"content": "Hello, world",
}
SIGN_TEST_EVENT = messages.NostrSignEvent(
address_n=parse_path("m/44h/1237h/0h/0/0"),
created_at=TEST_EVENT["created_at"],
kind=TEST_EVENT["kind"],
content=TEST_EVENT["content"],
tags=[
messages.NostrTag(key=t[0], value=t[1] if len(t) > 1 else None, extra=t[2:])
for t in TEST_EVENT["tags"]
],
)
@pytest.mark.parametrize("pubkey_hex", VECTORS)
def test_get_pubkey(client, pubkey_hex):
response = nostr.get_pubkey(
client,
n=parse_path("m/44h/1237h/0h/0/0"),
)
assert response == bytes.fromhex(pubkey_hex)
@pytest.mark.parametrize("pubkey_hex", VECTORS)
def test_sign_event(client, pubkey_hex):
response = nostr.sign_event(client, SIGN_TEST_EVENT)
assert response.pubkey == bytes.fromhex(pubkey_hex)
expected_id = (
sha256(
json.dumps(
[
0,
pubkey_hex,
TEST_EVENT["created_at"],
TEST_EVENT["kind"],
TEST_EVENT["tags"],
TEST_EVENT["content"],
],
separators=(",", ":"),
).encode()
)
.digest()
.hex()
)
assert response.id == expected_id
vk = VerifyingKey.from_string(
b("\x03") + bytes.fromhex(pubkey_hex),
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(bytes.fromhex(response.signature), bytes.fromhex(response.id))