feat(legacy): Implement SLIP-0019 proofs of ownership.

pull/2850/head
Andrew Kozlik 1 year ago committed by matejcik
parent 9732f6524a
commit fa2d618f7d

@ -0,0 +1 @@
Implement SLIP-0019 proofs of ownership for native SegWit.

@ -371,6 +371,14 @@ bool fsm_layoutVerifyMessage(const uint8_t *msg, uint32_t len) {
}
}
bool fsm_layoutCommitmentData(const uint8_t *msg, uint32_t len) {
if (is_valid_ascii(msg, len)) {
return fsm_layoutPaginated(_("Commitment data"), msg, len, true);
} else {
return fsm_layoutPaginated(_("Binary commitment data"), msg, len, false);
}
}
void fsm_msgRebootToBootloader(void) {
layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL,
_("Do you want to"), _("restart device in"),

@ -80,6 +80,8 @@ void fsm_msgTxAck(
void fsm_msgGetAddress(const GetAddress *msg);
void fsm_msgSignMessage(const SignMessage *msg);
void fsm_msgVerifyMessage(const VerifyMessage *msg);
void fsm_msgGetOwnershipId(const GetOwnershipId *msg);
void fsm_msgGetOwnershipProof(const GetOwnershipProof *msg);
// crypto
void fsm_msgCipherKeyValue(const CipherKeyValue *msg);

@ -183,6 +183,27 @@ bool fsm_checkCoinPath(const CoinInfo *coin, InputScriptType script_type,
return true;
}
bool fsm_checkScriptType(const CoinInfo *coin, InputScriptType script_type) {
if (!is_internal_input_script_type(script_type)) {
fsm_sendFailure(FailureType_Failure_DataError, _("Invalid script type"));
return false;
}
if (is_segwit_input_script_type(script_type) && !coin->has_segwit) {
fsm_sendFailure(FailureType_Failure_DataError,
_("Segwit not enabled on this coin"));
return false;
}
if (script_type == InputScriptType_SPENDTAPROOT && !coin->has_taproot) {
fsm_sendFailure(FailureType_Failure_DataError,
_("Taproot not enabled on this coin"));
return false;
}
return true;
}
void fsm_msgGetAddress(const GetAddress *msg) {
RESP_INIT(Address);
@ -373,3 +394,166 @@ void fsm_msgVerifyMessage(const VerifyMessage *msg) {
}
layoutHome();
}
bool fsm_getOwnershipId(uint8_t *script_pubkey, size_t script_pubkey_size,
uint8_t ownership_id[OWNERSHIP_ID_SIZE]) {
const char *OWNERSHIP_ID_KEY_PATH[] = {"SLIP-0019",
"Ownership identification key"};
uint8_t ownership_id_key[32] = {0};
if (!fsm_getSlip21Key(OWNERSHIP_ID_KEY_PATH, 2, ownership_id_key)) {
return false;
}
hmac_sha256(ownership_id_key, sizeof(ownership_id_key), script_pubkey,
script_pubkey_size, ownership_id);
return true;
}
void fsm_msgGetOwnershipId(const GetOwnershipId *msg) {
RESP_INIT(OwnershipId);
CHECK_INITIALIZED
CHECK_PIN
const CoinInfo *coin = fsm_getCoin(msg->has_coin_name, msg->coin_name);
if (!coin) return;
if (!fsm_checkCoinPath(coin, msg->script_type, msg->address_n_count,
msg->address_n, msg->has_multisig, false)) {
layoutHome();
return;
}
if (!fsm_checkScriptType(coin, msg->script_type)) {
layoutHome();
return;
}
HDNode *node = fsm_getDerivedNode(coin->curve_name, msg->address_n,
msg->address_n_count, NULL);
if (!node) return;
uint8_t script_pubkey[520] = {0};
pb_size_t script_pubkey_size = 0;
if (!get_script_pubkey(coin, node, msg->has_multisig, &msg->multisig,
msg->script_type, script_pubkey,
&script_pubkey_size)) {
fsm_sendFailure(FailureType_Failure_ProcessError,
_("Failed to derive scriptPubKey"));
layoutHome();
return;
}
if (!fsm_getOwnershipId(script_pubkey, script_pubkey_size,
resp->ownership_id.bytes)) {
return;
}
resp->ownership_id.size = 32;
msg_write(MessageType_MessageType_OwnershipId, resp);
layoutHome();
}
void fsm_msgGetOwnershipProof(const GetOwnershipProof *msg) {
RESP_INIT(OwnershipProof);
CHECK_INITIALIZED
CHECK_PIN
if (msg->has_multisig) {
// The legacy implementation currently only supports singlesig native segwit
// v0 and v1, the bare minimum for CoinJoin.
fsm_sendFailure(FailureType_Failure_DataError,
_("Multisig not supported."));
layoutHome();
return;
}
const CoinInfo *coin = fsm_getCoin(msg->has_coin_name, msg->coin_name);
if (!coin) return;
if (!fsm_checkCoinPath(coin, msg->script_type, msg->address_n_count,
msg->address_n, msg->has_multisig, false)) {
layoutHome();
return;
}
if (!fsm_checkScriptType(coin, msg->script_type)) {
layoutHome();
return;
}
HDNode *node = fsm_getDerivedNode(coin->curve_name, msg->address_n,
msg->address_n_count, NULL);
if (!node) return;
uint8_t script_pubkey[520] = {0};
pb_size_t script_pubkey_size = 0;
if (!get_script_pubkey(coin, node, msg->has_multisig, &msg->multisig,
msg->script_type, script_pubkey,
&script_pubkey_size)) {
fsm_sendFailure(FailureType_Failure_ProcessError,
_("Failed to derive scriptPubKey"));
layoutHome();
return;
}
uint8_t ownership_id[OWNERSHIP_ID_SIZE] = {0};
if (!fsm_getOwnershipId(script_pubkey, script_pubkey_size, ownership_id)) {
return;
}
// Providing an ownership ID is optional in case of singlesig, but if one is
// provided, then it should match.
if (msg->ownership_ids_count) {
if (msg->ownership_ids_count != 1 ||
msg->ownership_ids[0].size != sizeof(ownership_id) ||
memcmp(ownership_id, msg->ownership_ids[0].bytes,
sizeof(ownership_id)) != 0) {
fsm_sendFailure(FailureType_Failure_DataError,
_("Invalid ownership identifier"));
layoutHome();
return;
}
}
// In order to set the "user confirmation" bit in the proof, the user must
// actually confirm.
uint8_t flags = 0;
if (msg->user_confirmation) {
flags |= 1;
layoutConfirmOwnershipProof();
if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) {
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
layoutHome();
return;
}
if (msg->has_commitment_data) {
if (!fsm_layoutCommitmentData(msg->commitment_data.bytes,
msg->commitment_data.size)) {
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
layoutHome();
return;
}
}
}
if (!get_ownership_proof(coin, msg->script_type, node, flags, ownership_id,
script_pubkey, script_pubkey_size,
msg->commitment_data.bytes,
msg->commitment_data.size, resp)) {
fsm_sendFailure(FailureType_Failure_ProcessError, _("Signing failed"));
layoutHome();
return;
}
msg_write(MessageType_MessageType_OwnershipProof, resp);
layoutHome();
}

@ -1315,3 +1315,9 @@ void layoutConfirmHash(const BITMAP *icon, const char *description,
layoutButtonYes(_("Confirm"), &bmp_btn_confirm);
oledRefresh();
}
void layoutConfirmOwnershipProof(void) {
layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL,
_("Do you want to"), _("create a proof of"),
_("ownership?"), NULL, NULL, NULL);
}

@ -115,6 +115,8 @@ void layoutConfirmSafetyChecks(SafetyCheckLevel safety_checks_level);
void layoutConfirmHash(const BITMAP *icon, const char *description,
const uint8_t *hash, uint32_t len);
void layoutConfirmOwnershipProof(void);
const char **split_message(const uint8_t *msg, uint32_t len, uint32_t rowlen);
const char **split_message_hex(const uint8_t *msg, uint32_t len);

@ -4,7 +4,7 @@ endif
SKIPPED_MESSAGES := Binance Cardano DebugMonero Eos Monero Ontology Ripple SdProtect Tezos WebAuthn \
DebugLinkRecordScreen DebugLinkEraseSdCard DebugLinkWatchLayout \
GetOwnershipProof OwnershipProof GetOwnershipId OwnershipId AuthorizeCoinJoin DoPreauthorized \
AuthorizeCoinJoin DoPreauthorized \
CancelAuthorization DebugLinkLayout GetNonce SetBusy UnlockPath \
TxAckInput TxAckOutput TxAckPrev TxAckPaymentRequest \
EthereumSignTypedData EthereumTypedDataStructRequest EthereumTypedDataStructAck \

@ -34,8 +34,8 @@ TxInputType.address_n max_count:8
TxInputType.prev_hash max_size:32
TxInputType.script_sig max_size:1650
TxInputType.witness max_size:109
TxInputType.ownership_proof max_size:171
TxInputType.commitment_data max_size:32
TxInputType.ownership_proof max_size:147
TxInputType.commitment_data max_size:70
TxInputType.orig_hash max_size:32
TxInputType.script_pubkey max_size:520
@ -62,8 +62,8 @@ TxInput.address_n max_count:8
TxInput.prev_hash max_size:32
TxInput.script_sig max_size:1650
TxInput.witness max_size:109
TxInput.ownership_proof max_size:171
TxInput.commitment_data max_size:32
TxInput.ownership_proof max_size:147
TxInput.commitment_data max_size:70
TxInput.orig_hash max_size:32
TxInput.script_pubkey max_size:520
@ -79,12 +79,21 @@ PrevOutput.script_pubkey max_size:520
TxAckPrevExtraDataWrapper.extra_data_chunk type:FT_IGNORE
GetOwnershipId.address_n max_count:8
GetOwnershipId.coin_name max_size:21
OwnershipId.ownership_id max_size:32
GetOwnershipProof.address_n max_count:8
GetOwnershipProof.coin_name max_size:21
GetOwnershipProof.ownership_ids max_count:15 max_size:32
GetOwnershipProof.commitment_data max_size:70
OwnershipProof.ownership_proof max_size:147
OwnershipProof.signature max_size:71
# Unused messages.
AuthorizeCoinJoin skip_message:true
GetOwnershipId skip_message:true
OwnershipId skip_message:true
GetOwnershipProof skip_message:true
OwnershipProof skip_message:true
TxAckPaymentRequest skip_message:true
PaymentRequestMemo skip_message:true
CoinJoinRequest skip_message:true

@ -79,6 +79,8 @@
static const uint8_t segwit_header[2] = {0, 1};
static const uint8_t SLIP19_VERSION_MAGIC[] = {0x53, 0x4c, 0x00, 0x19};
static inline uint32_t op_push_size(uint32_t i) {
if (i < 0x4C) {
return 1;
@ -413,6 +415,20 @@ int compile_output(const CoinInfo *coin, AmountUnit amount_unit,
return out->script_pubkey.size;
}
int get_script_pubkey(const CoinInfo *coin, HDNode *node, bool has_multisig,
const MultisigRedeemScriptType *multisig,
InputScriptType script_type, uint8_t *script_pubkey,
pb_size_t *script_pubkey_size) {
char address[MAX_ADDR_SIZE] = {0};
bool res = true;
res = res && (hdnode_fill_public_key(node) == 0);
res = res && compute_address(coin, script_type, node, has_multisig, multisig,
address);
res = res && address_to_script_pubkey(coin, address, script_pubkey,
script_pubkey_size);
return res;
}
int fill_input_script_pubkey(const CoinInfo *coin, const HDNode *root,
TxInputType *in) {
if (in->script_type == InputScriptType_EXTERNAL) {
@ -422,16 +438,13 @@ int fill_input_script_pubkey(const CoinInfo *coin, const HDNode *root,
static CONFIDENTIAL HDNode node;
memcpy(&node, root, sizeof(HDNode));
char address[MAX_ADDR_SIZE] = {0};
bool res = true;
int res = true;
res = res && hdnode_private_ckd_cached(&node, in->address_n,
in->address_n_count, NULL);
res = res && (hdnode_fill_public_key(&node) == 0);
res = res && compute_address(coin, in->script_type, &node, in->has_multisig,
&in->multisig, address);
res = res && get_script_pubkey(coin, &node, in->has_multisig, &in->multisig,
in->script_type, in->script_pubkey.bytes,
&in->script_pubkey.size);
memzero(&node, sizeof(node));
res = res && address_to_script_pubkey(coin, address, in->script_pubkey.bytes,
&in->script_pubkey.size);
in->has_script_pubkey = res;
return res;
}
@ -1222,3 +1235,78 @@ uint32_t tx_decred_witness_weight(const TxInputType *txinput) {
return 4 * size;
}
#endif
bool get_ownership_proof(const CoinInfo *coin, InputScriptType script_type,
const HDNode *node, uint8_t flags,
const uint8_t ownership_id[OWNERSHIP_ID_SIZE],
const uint8_t *script_pubkey,
size_t script_pubkey_size,
const uint8_t *commitment_data,
size_t commitment_data_size, OwnershipProof *out) {
size_t r = 0;
// Write versionMagic (4 bytes).
memcpy(out->ownership_proof.bytes + r, SLIP19_VERSION_MAGIC,
sizeof(SLIP19_VERSION_MAGIC));
r += sizeof(SLIP19_VERSION_MAGIC);
// Write flags (1 byte).
out->ownership_proof.bytes[r] = flags;
r += 1;
// Write number of ownership IDs (1 byte).
r += ser_length(1, out->ownership_proof.bytes + r);
// Write ownership ID (32 bytes).
memcpy(out->ownership_proof.bytes + r, ownership_id, OWNERSHIP_ID_SIZE);
r += OWNERSHIP_ID_SIZE;
// Compute sighash = SHA-256(proofBody || proofFooter).
Hasher hasher = {0};
uint8_t sighash[SHA256_DIGEST_LENGTH] = {0};
hasher_InitParam(&hasher, HASHER_SHA2, NULL, 0);
hasher_Update(&hasher, out->ownership_proof.bytes, r);
tx_script_hash(&hasher, script_pubkey_size, script_pubkey);
tx_script_hash(&hasher, commitment_data_size, commitment_data);
hasher_Final(&hasher, sighash);
// Write proofSignature.
if (script_type == InputScriptType_SPENDWITNESS) {
if (!tx_sign_ecdsa(coin->curve->params, node->private_key, sighash,
out->signature.bytes, &out->signature.size)) {
return false;
}
// Write length-prefixed empty scriptSig (1 byte).
r += ser_length(0, out->ownership_proof.bytes + r);
// Write
// 1. number of stack items (1 byte)
// 2. signature + sighash type length (1 byte)
// 3. DER-encoded signature (max. 71 bytes)
// 4. sighash type (1 byte)
// 5. public key length (1 byte)
// 6. public key (33 bytes)
r += serialize_p2wpkh_witness(out->signature.bytes, out->signature.size,
node->public_key, 33, SIGHASH_ALL,
out->ownership_proof.bytes + r);
} else if (script_type == InputScriptType_SPENDTAPROOT) {
if (!tx_sign_bip340(node->private_key, sighash, out->signature.bytes,
&out->signature.size)) {
return false;
}
// Write length-prefixed empty scriptSig (1 byte).
r += ser_length(0, out->ownership_proof.bytes + r);
// Write
// 1. number of stack items (1 byte)
// 2. signature length (1 byte)
// 3. signature (64 bytes)
r += serialize_p2tr_witness(out->signature.bytes, out->signature.size, 0,
out->ownership_proof.bytes + r);
} else {
return false;
}
out->ownership_proof.size = r;
return true;
}

@ -30,6 +30,8 @@
#define TX_OVERWINTERED 0x80000000
#define OWNERSHIP_ID_SIZE 32
enum {
// Signature hash type with the same semantics as SIGHASH_ALL, but instead of
// having to include the byte in the signature, it is implied.
@ -102,6 +104,10 @@ bool tx_sign_bip340(const uint8_t *private_key, const uint8_t *hash,
int compile_output(const CoinInfo *coin, AmountUnit amount_unit,
const HDNode *root, TxOutputType *in, TxOutputBinType *out,
bool needs_confirm);
int get_script_pubkey(const CoinInfo *coin, HDNode *node, bool has_multisig,
const MultisigRedeemScriptType *multisig,
InputScriptType script_type, uint8_t *script_pubkey,
pb_size_t *script_pubkey_size);
int fill_input_script_pubkey(const CoinInfo *coin, const HDNode *root,
TxInputType *in);
@ -139,5 +145,12 @@ void tx_hash_final(TxStruct *t, uint8_t *hash, bool reverse);
uint32_t tx_input_weight(const CoinInfo *coin, const TxInputType *txinput);
uint32_t tx_output_weight(const CoinInfo *coin, const TxOutputType *txoutput);
uint32_t tx_decred_witness_weight(const TxInputType *txinput);
bool get_ownership_proof(const CoinInfo *coin, InputScriptType script_type,
const HDNode *node, uint8_t flags,
const uint8_t ownership_id[OWNERSHIP_ID_SIZE],
const uint8_t *script_pubkey,
size_t script_pubkey_size,
const uint8_t *commitment_data,
size_t commitment_data_size, OwnershipProof *out);
#endif

@ -21,8 +21,6 @@ from trezorlib.debuglink import TrezorClientDebugLink as Client
from trezorlib.exceptions import TrezorFailure
from trezorlib.tools import parse_path
pytestmark = pytest.mark.skip_t1
def test_p2wpkh_ownership_id(client: Client):
ownership_id = btc.get_ownership_id(

@ -101,6 +101,14 @@
"T1_bitcoin-test_getaddress_show.py::test_show_multisig_15": "40f652d0e899d528605f472f47ec6cb727ced8fe90588a8904b46ed39c2088e8",
"T1_bitcoin-test_getaddress_show.py::test_show_multisig_3": "05e4e5cd014bf96373788e4563a48f5cdb4c54d358a88e8a29247c2825c5669e",
"T1_bitcoin-test_getaddress_show.py::test_show_unrecognized_path": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"T1_bitcoin-test_getownershipproof.py::test_attack_ownership_id": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"T1_bitcoin-test_getownershipproof.py::test_confirm_ownership_proof": "7e6fcf4df33bf876103f0f8b547c0bd7e5babd34cfe5be43d62c08fbc67f525b",
"T1_bitcoin-test_getownershipproof.py::test_confirm_ownership_proof_with_data": "c0c6509ae54e7199cb457ababbc71cdb0ef1c53d535ba4b9fbf59db2cb5171cd",
"T1_bitcoin-test_getownershipproof.py::test_fake_ownership_id": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"T1_bitcoin-test_getownershipproof.py::test_p2tr_ownership_id": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"T1_bitcoin-test_getownershipproof.py::test_p2tr_ownership_proof": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"T1_bitcoin-test_getownershipproof.py::test_p2wpkh_ownership_id": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"T1_bitcoin-test_getownershipproof.py::test_p2wpkh_ownership_proof": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"T1_bitcoin-test_getpublickey.py::test_get_public_node[Bitcoin-76067358-path0-xpub6BiVtCpG9fQPx-40a56ca3": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"T1_bitcoin-test_getpublickey.py::test_get_public_node[Bitcoin-76067358-path1-xpub6BiVtCpG9fQQR-1abafc98": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"T1_bitcoin-test_getpublickey.py::test_get_public_node[Bitcoin-76067358-path2-xpub6FVDRC1jiWNTu-47a67414": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",

Loading…
Cancel
Save