Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support external inputs #1080

Merged
merged 29 commits into from Jul 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
33dd01f
core/sign_tx: Use varint length encoding for witness stack items.
andrewkozlik May 25, 2020
4a50bfe
core/crypto: Fix endianity in DER length encoding.
andrewkozlik May 25, 2020
9a06c00
common: Add witness field to TxInputType.
andrewkozlik May 25, 2020
6f92335
core/sign_tx: Check script_pubkeys of inputs.
andrewkozlik May 25, 2020
5f3e8b0
core/common: Add BytearrayReader and basic reader functions.
andrewkozlik Jun 24, 2020
a8b60d5
core/crypto: Add functions for verifying DER encoded signatures.
andrewkozlik May 25, 2020
ecb146d
core/bitcoin: Move script types from helpers to common.
andrewkozlik Jun 10, 2020
7891077
core/bitcoin: Replace TxInputType parameter in input_derive_script.
andrewkozlik Jun 10, 2020
f6fcd29
core/bitcoin: Implement parsing of scripts and witnesses for signatur…
andrewkozlik Jun 20, 2020
9a16d1e
core/bitcoin: Clarify hash_type vs. sighash_type terminology.
andrewkozlik Jun 26, 2020
679f11b
core/bitcoin: Implement signature verifier.
andrewkozlik Jun 22, 2020
10a9048
core/sign_tx: Factor out get_legacy_tx_digest() from sign_nonsegwit_i…
andrewkozlik Jun 22, 2020
1d442e1
core/sign_tx: Implement support for signed external inputs.
andrewkozlik Jun 20, 2020
9a38ca4
common/protob: Add GetOwnershipProof message.
andrewkozlik Jun 9, 2020
442ecaa
core/bitcoin: Implement BIP-322 SignatureProof container.
andrewkozlik Jun 30, 2020
761d0dc
core/bitcoin: Implement generation and verification of SLIP-0019 proo…
andrewkozlik Jun 9, 2020
6135e35
core/tests: Add unit tests for SLIP-0019 proofs of ownership.
andrewkozlik Jun 20, 2020
9041a67
common: Add ownership_proof field to TxInputType.
andrewkozlik Jun 15, 2020
f9abcce
core/bitcoin: Add support for external inputs with proof of non-owner…
andrewkozlik Jun 15, 2020
7b8b19b
tests/sign_tx: Don't expect signatures to be returned for external in…
andrewkozlik May 25, 2020
a6d2d71
tests/sign_tx: Add device tests for transactions with external inputs.
andrewkozlik May 25, 2020
9d09cb6
common/protob: Add GetOwnershipId message.
andrewkozlik Jun 16, 2020
c0384af
core/bitcoin: Implement GetOwnershipId message.
andrewkozlik Jun 16, 2020
5e91254
python: Add get_ownership_id() and get_ownership_proof() to trezorlib.
andrewkozlik Jun 16, 2020
bf5b8a5
tests: Add device tests for generation of proofs of ownership.
andrewkozlik Jun 16, 2020
8c2864e
core/bitcoin: Add special confirmation screen for transactions with e…
andrewkozlik Jun 17, 2020
51d380a
tests/sign_tx: Add device tests for transactions with external inputs…
andrewkozlik Jun 20, 2020
4c958e6
core: Update changelog.
andrewkozlik Jun 22, 2020
de46895
core/bitcoin: Rename witness_p2wsh() to witness_multisig().
andrewkozlik Jun 30, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
49 changes: 49 additions & 0 deletions common/protob/messages-bitcoin.proto
Expand Up @@ -82,6 +82,27 @@ message Address {
required string address = 1; // Coin address in Base58 encoding
}

/**
* Request: Ask device for ownership identifier corresponding to scriptPubKey for address_n path
* @start
* @next OwnershipId
* @next Failure
*/
message GetOwnershipId {
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
optional string coin_name = 2 [default='Bitcoin']; // coin to use
optional MultisigRedeemScriptType multisig = 3; // filled if we are dealing with a multisig scriptPubKey
optional InputScriptType script_type = 4 [default=SPENDADDRESS]; // used to distinguish between various address formats (non-segwit, segwit, etc.)
}

/**
* Response: Contains the ownership identifier for the scriptPubKey and device private seed
* @end
*/
message OwnershipId {
required bytes ownership_id = 1; // ownership identifier
}

/**
* Request: Ask device to sign message
* @start
Expand Down Expand Up @@ -216,6 +237,9 @@ message TxAck {
// optional uint32 decred_script_version = 10; // only for Decred // deprecated -> only 0 is supported
// optional bytes prev_block_hash_bip115 = 11; // BIP-115 support dropped
// optional uint32 prev_block_height_bip115 = 12; // BIP-115 support dropped
optional bytes witness = 13; // witness data, only set for EXTERNAL inputs
optional bytes ownership_proof = 14; // SLIP-0019 proof of ownership, only set for EXTERNAL inputs

}
/**
* Structure representing compiled transaction output
Expand Down Expand Up @@ -249,3 +273,28 @@ message TxAck {
}
}
}

/**
* Request: Ask device for a proof of ownership corresponding to address_n path
* @start
* @next OwnershipProof
* @next Failure
*/
message GetOwnershipProof {
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
optional string coin_name = 2 [default='Bitcoin']; // coin to use
optional InputScriptType script_type = 3 [default=SPENDWITNESS]; // used to distinguish between various scriptPubKey types
optional MultisigRedeemScriptType multisig = 4; // filled if proof is for a multisig address
optional bool user_confirmation = 5; // show a confirmation dialog and set the "user confirmation" bit in the proof
repeated bytes ownership_ids = 6; // list of ownership identifiers in case of multisig
optional bytes commitment_data = 7; // additional data to which the proof should commit
}

/**
* Response: Contains the proof of ownership
* @end
*/
message OwnershipProof {
required bytes ownership_proof = 1; // SLIP-0019 proof of ownership
required bytes signature = 2; // signature of the proof
}
4 changes: 4 additions & 0 deletions common/protob/messages.proto
Expand Up @@ -87,6 +87,10 @@ enum MessageType {
MessageType_SignMessage = 38 [(wire_in) = true];
MessageType_VerifyMessage = 39 [(wire_in) = true];
MessageType_MessageSignature = 40 [(wire_out) = true];
MessageType_GetOwnershipId = 43 [(wire_in) = true];
MessageType_OwnershipId = 44 [(wire_out) = true];
MessageType_GetOwnershipProof = 49 [(wire_in) = true];
MessageType_OwnershipProof = 50 [(wire_out) = true];

// Crypto
MessageType_CipherKeyValue = 23 [(wire_in) = true];
Expand Down
2 changes: 2 additions & 0 deletions core/ChangeLog
Expand Up @@ -12,6 +12,8 @@ _Most likely to be released on July 1st._
- Soft lock. #958
- Auto lock. #1027
- Dedicated `initialized` field in storage.
- Support EXTERNAL transaction inputs with a SLIP-0019 proof of ownership #1052
- Support pre-signed EXTERNAL transaction inputs

### Deprecated
- Deprecate `Overwintered` field in `SignTx` and `TxAck`.
Expand Down
2 changes: 2 additions & 0 deletions core/src/apps/bitcoin/__init__.py
Expand Up @@ -5,6 +5,8 @@
def boot() -> None:
wire.add(MessageType.GetPublicKey, __name__, "get_public_key")
wire.add(MessageType.GetAddress, __name__, "get_address")
wire.add(MessageType.GetOwnershipId, __name__, "get_ownership_id")
wire.add(MessageType.GetOwnershipProof, __name__, "get_ownership_proof")
wire.add(MessageType.SignTx, __name__, "sign_tx")
wire.add(MessageType.SignMessage, __name__, "sign_message")
wire.add(MessageType.VerifyMessage, __name__, "verify_message")
42 changes: 40 additions & 2 deletions core/src/apps/bitcoin/common.py
Expand Up @@ -3,13 +3,51 @@
from trezor import wire
from trezor.crypto import bech32, bip32, der
from trezor.crypto.curve import secp256k1
from trezor.messages import InputScriptType, OutputScriptType
from trezor.utils import ensure

from apps.common.coininfo import CoinInfo
if False:
from apps.common.coininfo import CoinInfo
from typing import Dict
from trezor.messages.TxInputType import EnumTypeInputScriptType
from trezor.messages.TxOutputType import EnumTypeOutputScriptType

# Default signature hash type in Bitcoin which signs all inputs and all outputs of the transaction.
SIGHASH_ALL = const(0x01)

# supported witness version for bech32 addresses
_BECH32_WITVER = const(0x00)

MULTISIG_INPUT_SCRIPT_TYPES = (
InputScriptType.SPENDMULTISIG,
InputScriptType.SPENDP2SHWITNESS,
InputScriptType.SPENDWITNESS,
)
MULTISIG_OUTPUT_SCRIPT_TYPES = (
OutputScriptType.PAYTOMULTISIG,
OutputScriptType.PAYTOP2SHWITNESS,
OutputScriptType.PAYTOWITNESS,
)

CHANGE_OUTPUT_TO_INPUT_SCRIPT_TYPES = {
OutputScriptType.PAYTOADDRESS: InputScriptType.SPENDADDRESS,
OutputScriptType.PAYTOMULTISIG: InputScriptType.SPENDMULTISIG,
OutputScriptType.PAYTOP2SHWITNESS: InputScriptType.SPENDP2SHWITNESS,
OutputScriptType.PAYTOWITNESS: InputScriptType.SPENDWITNESS,
} # type: Dict[EnumTypeOutputScriptType, EnumTypeInputScriptType]
INTERNAL_INPUT_SCRIPT_TYPES = tuple(CHANGE_OUTPUT_TO_INPUT_SCRIPT_TYPES.values())
CHANGE_OUTPUT_SCRIPT_TYPES = tuple(CHANGE_OUTPUT_TO_INPUT_SCRIPT_TYPES.keys())

SEGWIT_INPUT_SCRIPT_TYPES = (
InputScriptType.SPENDP2SHWITNESS,
InputScriptType.SPENDWITNESS,
)

NONSEGWIT_INPUT_SCRIPT_TYPES = (
InputScriptType.SPENDADDRESS,
InputScriptType.SPENDMULTISIG,
)


def ecdsa_sign(node: bip32.HDNode, digest: bytes) -> bytes:
sig = secp256k1.sign(node.private_key(), digest)
Expand All @@ -28,7 +66,7 @@ def ecdsa_hash_pubkey(pubkey: bytes, coin: CoinInfo) -> bytes:
return coin.script_hash(pubkey)


def encode_bech32_address(prefix: str, script: bytes) -> bytes:
def encode_bech32_address(prefix: str, script: bytes) -> str:
address = bech32.encode(prefix, _BECH32_WITVER, script)
if address is None:
raise wire.ProcessError("Invalid address")
Expand Down
41 changes: 41 additions & 0 deletions core/src/apps/bitcoin/get_ownership_id.py
@@ -0,0 +1,41 @@
from trezor import wire
from trezor.messages.GetOwnershipId import GetOwnershipId
from trezor.messages.OwnershipId import OwnershipId

from apps.common import coininfo
from apps.common.paths import validate_path

from . import addresses, common, scripts
from .keychain import with_keychain
from .ownership import get_identifier

if False:
from apps.common.seed import Keychain


@with_keychain
async def get_ownership_id(
ctx, msg: GetOwnershipId, keychain: Keychain, coin: coininfo.CoinInfo
) -> OwnershipId:
await validate_path(
ctx,
addresses.validate_full_path,
keychain,
msg.address_n,
coin.curve_name,
coin=coin,
script_type=msg.script_type,
)

if msg.script_type not in common.INTERNAL_INPUT_SCRIPT_TYPES:
raise wire.DataError("Invalid script type")

if msg.script_type in common.SEGWIT_INPUT_SCRIPT_TYPES and not coin.segwit:
raise wire.DataError("Segwit not enabled on this coin")

node = keychain.derive(msg.address_n)
address = addresses.get_address(msg.script_type, coin, node, msg.multisig)
script_pubkey = scripts.output_derive_script(address, coin)
ownership_id = get_identifier(script_pubkey, keychain)

return OwnershipId(ownership_id=ownership_id)
92 changes: 92 additions & 0 deletions core/src/apps/bitcoin/get_ownership_proof.py
@@ -0,0 +1,92 @@
from ubinascii import hexlify

from trezor import ui, wire
from trezor.messages.GetOwnershipProof import GetOwnershipProof
from trezor.messages.OwnershipProof import OwnershipProof
from trezor.ui.text import Text

from apps.common import coininfo
from apps.common.confirm import require_confirm
from apps.common.paths import validate_path

from . import addresses, common, scripts
from .keychain import with_keychain
from .ownership import generate_proof, get_identifier

if False:
from apps.common.seed import Keychain

# Maximum number of characters per line in monospace font.
_MAX_MONO_LINE = 18


@with_keychain
async def get_ownership_proof(
ctx, msg: GetOwnershipProof, keychain: Keychain, coin: coininfo.CoinInfo
) -> OwnershipProof:
await validate_path(
ctx,
addresses.validate_full_path,
keychain,
msg.address_n,
coin.curve_name,
coin=coin,
script_type=msg.script_type,
)

if msg.script_type not in common.INTERNAL_INPUT_SCRIPT_TYPES:
raise wire.DataError("Invalid script type")

if msg.script_type in common.SEGWIT_INPUT_SCRIPT_TYPES and not coin.segwit:
raise wire.DataError("Segwit not enabled on this coin")

node = keychain.derive(msg.address_n)
address = addresses.get_address(msg.script_type, coin, node, msg.multisig)
script_pubkey = scripts.output_derive_script(address, coin)
ownership_id = get_identifier(script_pubkey, keychain)
onvej-sl marked this conversation as resolved.
Show resolved Hide resolved

# If the scriptPubKey is multisig, then the caller has to provide
# ownership IDs, otherwise providing an ID is optional.
if msg.multisig:
if ownership_id not in msg.ownership_ids:
raise wire.DataError("Missing ownership identifier")
elif msg.ownership_ids:
if msg.ownership_ids != [ownership_id]:
raise wire.DataError("Invalid ownership identifier")
else:
msg.ownership_ids = [ownership_id]

# In order to set the "user confirmation" bit in the proof, the user must actually confirm.
if msg.user_confirmation:
matejcik marked this conversation as resolved.
Show resolved Hide resolved
text = Text("Proof of ownership", ui.ICON_CONFIG)
text.normal("Do you want to create a")
if not msg.commitment_data:
text.normal("proof of ownership?")
else:
hex_data = hexlify(msg.commitment_data).decode()
text.normal("proof of ownership for:")
if len(hex_data) > 3 * _MAX_MONO_LINE:
text.mono(hex_data[0:_MAX_MONO_LINE])
text.mono(
hex_data[_MAX_MONO_LINE : 3 * _MAX_MONO_LINE // 2 - 1]
+ "..."
+ hex_data[-3 * _MAX_MONO_LINE // 2 + 2 : -_MAX_MONO_LINE]
)
text.mono(hex_data[-_MAX_MONO_LINE:])
else:
text.mono(hex_data)

await require_confirm(ctx, text)

ownership_proof, signature = generate_proof(
node,
msg.script_type,
msg.multisig,
coin,
msg.user_confirmation,
msg.ownership_ids,
script_pubkey,
msg.commitment_data,
)

return OwnershipProof(ownership_proof=ownership_proof, signature=signature)
2 changes: 2 additions & 0 deletions core/src/apps/bitcoin/keychain.py
Expand Up @@ -32,6 +32,8 @@ def get_namespaces_for_coin(coin: coininfo.CoinInfo):
# m/48'/slip44' (/account'/script_type'/change/addr)
namespaces.append((curve, [48 | HARDENED, slip44_id]))

namespaces.append(("slip21", [b"SLIP-0019"]))

if coin.segwit:
# BIP-49 - p2sh segwit: m/49'/slip44' (/account'/change/addr)
namespaces.append((curve, [49 | HARDENED, slip44_id]))
Expand Down