Skip to content

Latest commit

 

History

History
341 lines (240 loc) · 23.8 KB

slip-0019.md

File metadata and controls

341 lines (240 loc) · 23.8 KB

SLIP-0019 : Proof of Ownership

Number:  SLIP-0019
Title:   Proof of Ownership
Type:    Standard
Status:  Accepted
Authors: Andrew Kozlik <[email protected]>
         Stepan Snigirev <[email protected]>
         Ondrej Vejpustek <[email protected]>
         Pavol Rusnak <[email protected]>
Created: 2019-04-25

Abstract

This specification defines the format for a proof of ownership which can be passed to a hierarchical deterministic wallet together with each input of an unsigned transaction. This proof allows the wallet to determine whether it is able to spend the given input or not. It also allows third parties to verify that a user has the ability to spend the input.

Motivation

In certain applications like CoinJoin and Lightning, a wallet has to sign transactions containing external inputs. To calculate the actual amount the user is spending, the wallet needs to reliably determine for each input whether it belongs to the wallet or not. Without such a mechanism an attacker can deceive the wallet into displaying incorrect information about the amount being spent, which can result in theft of user funds. This was first recognized in a bitcoin-dev mailing list discussion.

For example, in a CoinJoin transaction an attacker can construct a transaction with inputs in1 and in2 of identical value belonging to the user and two outputs of identical value, user_out belonging to the user and attacker_out belonging to the attacker. If such a transaction is sent to a hardware wallet twice with in1 marked as external the first time and in2 marked as external the second time, then the hardware wallet will display two signing requests to the user with a spending amount of in2 - user_out and in1 - user_out, respectively. The user will think that they are signing two different CoinJoin transactions and spending in1 + in2 - 2*user_out for the fees, while in reality they are signing two different inputs to a single transaction and sending half of the amount to the attacker.

To mitigate such an attack, the hardware wallet needs to ascertain non-ownership of all inputs which are claimed to be external. In case of hierarchical deterministic wallets it is generally not feasible to ascertain this solely based on the scriptPubKey of the UTXO, because it would require searching through billions of BIP32 derivation paths. Furthermore, even though CoinJoin transactions currently work only with P2WPKH addresses, other applications may require more complicated inputs including multi-signature and Schnorr multi-signature in the future.

A CoinJoin coordinator can also benefit from such a proof to verify that the CoinJoin participant is able and willing to sign the input. This verification helps to mitigate denial-of-service attacks as the attacker has to use a limited UTXO set that they control and in case of misbehavior this UTXO set gets banned.

Proof of ownership format

A proof of ownership consists of a proof body and a signature. The proof body contains one or more ownership identifiers which allow a wallet to efficiently determine whether or not it is able to spend a UTXO having a given scriptPubKey. The proof signature affirms that the proof body can be trusted to have been generated by the true owner of the UTXO.

proofOfOwnership = proofBody || proofSignature

Ownership identifier

Let k be a secret ownership identification key derived from the wallet's master secret using the SLIP-0021 method for hierarchical derivation of symmetric keys as:

k = Key(m/"SLIP-0019"/"Ownership identification key")

The ownership identifier for a scriptPubKey is computed as:

id = HMAC-SHA256(key = k, msg = scriptPubKey)

In case of m-of-n multi-signature scriptPubKeys the proof of ownership SHOULD contain the ownership identifiers of all n co-owners of that scriptPubKey. See Identifier inclusion for further details.

A wallet MUST NOT produce and reveal an ownership identifier for a scriptPubKey which it does not control. Such a fake ownership identifier can be used to mount a denial-of-service attack.

Proof body

The proofBody is a concatenation of the following fields:

  • versionMagic (4 bytes): b"\x53\x4c\x00\x19" (this is "SL" followed by 0019 in compressed numeric form as an abbreviation for "SLIP-0019").
  • flags (1 byte, bit 0 is the least significant bit):
    • Bit 0: User confirmation
      • 0 means the proof was generated without user confirmation.
      • 1 means the user confirmed the generation of the proof.
    • Bits 1 to 7: Reserved for future use (all must be 0).
  • n (VarInt): the number of ownership identifiers which follow. The VarInt MUST be encoded in the fewest possible number of bytes.
  • id1 || id2 || ... || idn (32 bytes each): concatenation of the ownership identifiers for the given scriptPubKey, one for each co-owner, see Identifier inclusion for further details.

Proof footer

The proofFooter is a concatenation of the following fields:

  • scriptPubKey (length-prefixed string).
  • commitmentData (length-prefixed string), any additional data to which the proof should commit, see below.

The proof footer is included only in the sighash computation. It is not part of the proof of ownership, because the verifier of the proof should obtain these fields externally based on the context in which the proof is provided. Namely the scriptPubKey should be obtained by looking up the output being spent and the commitmentData is given by the application context. Variable-length fields are encoded the same way as in Bitcoin transactions, as a length-prefixed string, where the length is encoded as a variable-length integer (VarInt).

Proof signature

The concatenation of the proofBody and proofFooter is signed using the Generic Signed Message Format as defined in the original BIP-0322 until October 2020, when the BIP-0322 specification was rewritten to use the transaction-based approach. The proofSignature is the SignatureProof container defined in the original BIP-0322 using the sighash computed as:

sighash = SHA-256(proofBody || proofFooter)

Additional commitment data

The content of the commitmentData field is application-specific. If an application does not define the content of this field, then a zero-length string should be used by default.

In case of CoinJoin transactions the commitmentData SHOULD contain a globally unique PSBT identifier (psbtId). The purpose of such an identifier is to prevent an attacker from causing denial of service by registering an input into a different CoinJoin transaction than the one for which the input was intended. The user should explicitly confirm the generation of the proof and the commitmentData value to affirm their intent to participate in the given CoinJoin transaction.

The psbtId is not to be confused with TXID, which is the hash of a transaction's data. Since the psbtId needs to be known before the transaction is created, it cannot be derived from the transaction data but needs to be generated as a nonce. For example:

  1. The concatenation of a globally unique CoinJoin server identifier (192 bits) with a sequential round identifier (64 bits).
  2. A random 256 bit value.

Proof construction

Single-signature scriptPubKeys

When constructing a proof of ownership for a single-signature scriptPubKey the inputs to the wallet are the flags, scriptPubKey, commitmentData and the BIP32 derivation path. The wallet takes the following steps:

  1. Ensure that bits 1 through 7 of flags are clear.
  2. Ensure that the wallet controls the private key to the provided scriptPubKey. This is typically done by using the provided BIP32 derivation path.
  3. If bit 0 (user confirmation) of flags is set, then prompt the user to confirm generation of the ownership proof with the given commitmentData. If the user does not confirm, then abort.
  4. Compute the ownership identifier for the scriptPubKey.
  5. Compile the proofBody and proofFooter, and generate the proofSignature.
  6. Return the proofBody and proofSignature.

Multi-signature scriptPubKeys

The construction of a proof of ownership for a m-of-n multi-signature scriptPubKey requires a signing coordinator, i.e. a watch-only software wallet. The signing coordinator is assumed to have obtained the ownership identifiers of all n co-owners in advance. These ownership identifiers should generally be produced at the time of the creation of the multi-signature address.

When constructing a proof of ownership, the signing coordinator prepares the proofBody and proofFooter and sends these to each signer together with any other required metadata, such as the BIP32 derivation path for the input. Each of the m signers then takes the following steps:

  1. Parse the proofBody and proofFooter. If versionMagic is not recognized or if any of the bits 1 through 7 of flags is set, then abort.
  2. Derive the ownership identifier using the scriptPubKey provided in the proofFooter.
  3. If the derived ownership identifier is not listed in the proofBody, then abort.
  4. If bit 0 (user confirmation) of flags is set, then prompt the user to confirm generation of the ownership proof with the given commitmentData. If the user does not confirm, then abort.
  5. Return the signature for the provided proofBody and proofFooter.

The signing coordinator collects all the signatures and combines them into a SignatureProof container to finalize the proof.

Proof usage

Verifying non-ownership of transaction inputs

When a wallet is requested to sign a transaction, each external input SHOULD be accompanied with a proof of ownership so that the wallet may ascertain non-ownership of such an input in order to correctly inform the user about the amount they are spending in the transaction. For each external input the wallet takes the following steps:

  1. By reliable means obtain the scriptPubKey of the UTXO being spent by that input. Prior to SegWit version 1 witness programs this step involves acquiring the full transaction being spent and verifying its hash against that which is given in the outpoint.
  2. Parse the proofBody. If versionMagic is not recognized or if any of the bits 1 through 7 of flags is set, then abort.
  3. Verify that the proofSignature is valid in accordance with BIP-0322 using the obtained scriptPubKey and the sighash as defined in the Proof signature section.
  4. Derive the ownership identifier using the wallet's ownership identification key and the obtained scriptPubKey.
  5. Verify that the derived ownership identifier is not included in the proofBody.

Verifying ability and intent to sign an input

Each input which is registered to take part in a CoinJoin transaction should be accompanied with a proof of ownership which affirms the owner's intent to take part, so as to mitigate denial-of-service attacks. The CoinJoin coordinator takes the following steps before registering an input:

  1. By reliable means obtain the scriptPubKey of the UTXO being spent by that input.
  2. Parse the proofBody. If versionMagic is not recognized or if any of the bits 1 through 7 of flags is set, then abort.
  3. Verify that bit 0 (user confirmation) of flags is set.
  4. Verify that the proofSignature is valid using the obtained scriptPubKey.

A proof of ownership commits to a particular scriptPubKey, which means that the proof is replayable for UTXOs with the same address. Nevertheless, freshness of such a proof is guaranteed if a nonce (such as the psbtId) is included in the commitmentData.

PSBT (BIP 174) extension

The following new global field type is added to the BIP-0174 specification:

  • Type: PSBT identifier PSBT_GLOBAL_PSBT_ID = 0x0A
    • Key: None. The key must only contain the 1 byte type.
      • {0x02}
    • Value: A globally unique PSBT identifier. This value should be used as the commitmentData in the proofFooter.
      • {psbtId}

The following new per-input field type is added to the BIP-0174 specification:

  • Type: Proof of ownership PSBT_IN_PROOF_OF_OWNERSHIP = 0x0A
    • Key: None. The key must only contain the 1 byte type.
      • {0x0A}
    • Value: The proofOfOwnership as defined above.
      • {proofOfOwnership}

Implementation considerations

Script evaluation on hardware wallets

Currently most hardware wallets do not support complete Bitcoin script verification, so initial deployment of proofs of ownership can be limited to a set of known scripts. In the future hardware wallets may implement miniscript verification, that will cover most of the use-cases known today.

Identifier inclusion

When generating a proof of ownership for m-of-n multi-signature scriptPubKeys the proof body SHOULD contain the ownership identifiers of all n co-owners of that scriptPubKey. Failing to include all ownership identifiers opens the door to the following attack.

For simplicity consider two equal-valued UTXOs A and B, both of which have the same 1-of-2 multi-signature scriptPubKey controlled by Users 1 and 2. The attacker requests a proof of ownership P1 from User 1 containing only User 1's ownership identifier. Similarly the attacker requests a proof of ownership P2 from User 2 containing only User 2's ownership identifier. The attacker then creates a CoinJoin transaction with inputs A and B and equal-valued outputs out_user and out_attacker, the former of which is a multi-signature scriptPubKey controlled by Users 1 and 2. User 1 is given the transaction to sign with proof P2 for the input spending B, and User 2 is given the same transaction to sign with proof P1 for the input spending A. User 1 perceives B as foreign, assumes they are transferring A to out_user and signs the input spending A. User 2 perceives A as foreign, assumes they are transferring B to out_user and signs the input spending B. As a result half of the amount from A and B is transferred to the attacker. This attack is extendable to more complex m-of-n multi-signatures.

In some cases there are legitimate reasons not to include the ownership identifier of a co-owner:

  1. The excluded co-owner does not support any kind of proof of ownership format and will never take part in a transaction containing external inputs. An example of this would be a cryptocurrency custody service which is included in the multi-signature setup only as a backup in case the key of one of the co-owners is lost.
  2. A co-owner is intentionally excluded to avoid signing failures due to input ownership collisions. Consider a user who is participating in a CoinJoin transaction with their UTXO A. At the same time this user happens to be a co-owner of another UTXO B being spent as an input in the same transaction. The user is not meant to be cosigning B, because this input was registered independently by a group of co-owners who did not expect the user to participate. Thus the user's wallet will recognize B as an input it co-owns, but it will not be able to sign because it was not given the corresponding BIP32 derivation path. Even if the path were to be provided, the user might not be willing to cosign due to confusion at the unexpected presence of the input amount supplied by B. As a result the CoinJoin transaction will fail to complete. Before excluding an ownership identifier on these grounds, the likelihood of this kind of scenario needs to be carefully weighed against the risk of the attack described above.

Test vectors

Test vector 1 (P2WPKH)

Input parameters

Parameter Value
BIP39 seed "all all all all all all all all all all all all"
Passphrase ""
Ownership ID key (hex) 0a115a171e30f8a740bae6c4144bec5dc1099ffa79b83dfb8aa3501d094de585
Path m/84'/0'/0'/1/0
scriptPubKey (hex) 0014b2f771c370ccf219cd3059cda92bdf7f00cf2103
User confirmation False
commitmentData ""

Proof of ownership (hex)

534c00190001a122407efc198211c81af4450f40b235d54775efd934d16b9e31c6ce9bad57070002483045022100e5eaf2cb0a473b4545115c7b85323809e75cb106175ace38129fd62323d73df30220363dbc7acb7afcda022b1f8d97acb8f47c42043cfe0595583aa26e30bc8b3bb50121032ef68318c8f6aaa0adec0199c69901f0db7d3485eb38d9ad235221dc3d61154b

Split into components:

Name Value
versionMagic 534c0019
flags 00
n 01
id a122407efc198211c81af4450f40b235d54775efd934d16b9e31c6ce9bad5707
scriptSig 00
witness 02483045022100e5eaf2cb0a473b4545115c7b85323809e75cb106175ace3812
9fd62323d73df30220363dbc7acb7afcda022b1f8d97acb8f47c42043cfe0595
583aa26e30bc8b3bb50121032ef68318c8f6aaa0adec0199c69901f0db7d3485
eb38d9ad235221dc3d61154b

Test vector 2 (P2WPKH nested in BIP16 P2SH)

Input parameters

Parameter Value
BIP39 seed "all all all all all all all all all all all all"
Passphrase ""
Ownership ID key (hex) 0a115a171e30f8a740bae6c4144bec5dc1099ffa79b83dfb8aa3501d094de585
Path m/49'/0'/0'/1/0
scriptPubKey (hex) a914b9ddc52a7d95ad46d474bfc7186d0150e15a499187
User confirmation True
commitmentData "TREZOR"

Proof of ownership (hex)

534c0019010192caf0b8daf78f1d388dbbceaec34bd2dabc31b217e32343663667f6694a3f4617160014e0cffbee1925a411844f44c3b8d81365ab51d03602483045022100a2649df21ed61bbae9aa488f041e39cf37d82af9c1050d9477faca6a963e6b0302204b24ea33fa7232c18305521bd6dafb82828ce6baab2e770991b350f40c47cb58012103a961687895a78da9aef98eed8e1f2a3e91cfb69d2f3cf11cbd0bb1773d951928

Split into components:

Name Value
versionMagic 534c0019
flags 01
n 01
id 92caf0b8daf78f1d388dbbceaec34bd2dabc31b217e32343663667f6694a3f46
scriptSig 17160014e0cffbee1925a411844f44c3b8d81365ab51d036
witness 02483045022100a2649df21ed61bbae9aa488f041e39cf37d82af9c1050d9477
faca6a963e6b0302204b24ea33fa7232c18305521bd6dafb82828ce6baab2e77
0991b350f40c47cb58012103a961687895a78da9aef98eed8e1f2a3e91cfb69d
2f3cf11cbd0bb1773d951928

Test vector 3 (P2PKH)

Input parameters

Parameter Value
BIP39 seed "all all all all all all all all all all all all"
Passphrase "TREZOR"
Ownership ID key (hex) 2d773852e0959b3c1bac15bd3a8ad410e2c6720befb4f7f428d74bdd5d6e4f1d
Path m/44'/0'/0'/1/0
scriptPubKey (hex) 76a9145a4deff88ada6705ed70835bc0db56a124b9cdcd88ac
User confirmation False
commitmentData ""

Proof of ownership (hex)

534c00190001ccc49ac5fede0efc80725fbda8b763d4e62a221c51cc5425076cffa7722c0bda6a47304402206682f40a12f3609a308acb872888470a07760f2f4790ee4ff62665a39c02a5fc022026f3f38a7c2b2668c2eff9cc1e712c7f254926a482bae411ad18947eba9fd21c012102f63159e21fbcb54221ec993def967ad2183a9c243c8bff6e7d60f4d5ed3b386500

Split into components:

Name Value
versionMagic 534c0019
flags 00
n 01
id ccc49ac5fede0efc80725fbda8b763d4e62a221c51cc5425076cffa7722c0bda
scriptSig 6a47304402206682f40a12f3609a308acb872888470a07760f2f4790ee4ff626
65a39c02a5fc022026f3f38a7c2b2668c2eff9cc1e712c7f254926a482bae411
ad18947eba9fd21c012102f63159e21fbcb54221ec993def967ad2183a9c243c
8bff6e7d60f4d5ed3b3865
witness 00

Test vector 4 (P2PWSH 2-of-3 multisig)

Input parameters

Parameter Value
BIP39 seed 1 "all all all all all all all all all all all all"
Passphrase 1 ""
Ownership ID key 1 (hex) 0a115a171e30f8a740bae6c4144bec5dc1099ffa79b83dfb8aa3501d094de585
BIP39 seed 2 "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
Passphrase 2 ""
Ownership ID key 2 (hex) cd50559c65666fd381e823b82fff04763465062c1ff4c93d3e147a306f884130
BIP39 seed 3 "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong"
Passphrase 3 ""
Ownership ID key 3 (hex) 64b3e4f003fd7dea4168dd19f85410ac3b1844abd1d7f9f3a74254a7852af725
Path m/84'/0'/0'/1/0
scriptPubKey (hex) 00209149b5bcaae8c876f1997ef6b60ec197475217fd3e736d4c54fcf49fe4f5213a
User confirmation False
commitmentData "TREZOR"

Proof of ownership (hex)

534c00190003309c4ffec5c228cc836b51d572c0a730dbabd39df9f01862502ac9eabcdeb94a46307177b959c48bf2eb516e0463bb651aad388c7f8f597320df7854212fa3443892f9573e08cedff9160b243759520733a980fed45b131a8bba171317ae5d94000400483045022100d2798dc4dcf90c51372141f5d870e39ae7408c72f82ab78761bf4cfa5b793d2902203962bf7d753ef2d28496b36e7153e57ab5dc5bc4de569345d4f135e8f772484101483045022100b33bee50faaefef3069d05333fbb271b7f361b95918ca0f90036b8e7af93b88d022016d86af97841e8049e065ee02608e353654e3c03f989f30f3f87fe160cce479f01695221032ef68318c8f6aaa0adec0199c69901f0db7d3485eb38d9ad235221dc3d61154b2103025324888e429ab8e3dbaf1f7802648b9cd01e9b418485c5fa4c1b9b5700e1a621033057150eb57e2b21d69866747f3d377e928f864fa88ecc5ddb1c0e501cce3f8153ae

Split into components:

Name Value
versionMagic 534c0019
flags 00
n 03
id1 309c4ffec5c228cc836b51d572c0a730dbabd39df9f01862502ac9eabcdeb94a
id2 46307177b959c48bf2eb516e0463bb651aad388c7f8f597320df7854212fa344
id3 3892f9573e08cedff9160b243759520733a980fed45b131a8bba171317ae5d94
scriptSig 00
witness 0400483045022100d2798dc4dcf90c51372141f5d870e39ae7408c72f82ab787
61bf4cfa5b793d2902203962bf7d753ef2d28496b36e7153e57ab5dc5bc4de56
9345d4f135e8f772484101483045022100b33bee50faaefef3069d05333fbb27
1b7f361b95918ca0f90036b8e7af93b88d022016d86af97841e8049e065ee026
08e353654e3c03f989f30f3f87fe160cce479f01695221032ef68318c8f6aaa0
adec0199c69901f0db7d3485eb38d9ad235221dc3d61154b2103025324888e42
9ab8e3dbaf1f7802648b9cd01e9b418485c5fa4c1b9b5700e1a621033057150e
b57e2b21d69866747f3d377e928f864fa88ecc5ddb1c0e501cce3f8153ae

Test vector 5 (P2TR)

Input parameters

Parameter Value
BIP39 seed "all all all all all all all all all all all all"
Passphrase ""
Ownership ID key (hex) dc18066224b9e30e306303436dc18ab881c7266c13790350a3fe415e438135ec
Path m/86'/0'/0'/1/0
scriptPubKey (hex) 51204102897557de0cafea0a8401ea5b59668eccb753e4b100aebe6a19609f3cc79f
User confirmation False
commitmentData ""

Proof of ownership (hex)

534c00190001dc18066224b9e30e306303436dc18ab881c7266c13790350a3fe415e438135ec0001406cd08474ea019c9ab4b9b7b76ec03c4dd4db76abc3a460434a91cfc1b190174949eb7111c8e762407730a215421a0da0b5e01f48de62d7ccea0abea046e2a496

Split into components:

Name Value
versionMagic 534c0019
flags 00
n 01
id dc18066224b9e30e306303436dc18ab881c7266c13790350a3fe415e438135ec
scriptSig 00
witness 01406cd08474ea019c9ab4b9b7b76ec03c4dd4db76abc3a460434a91cfc1b190
174949eb7111c8e762407730a215421a0da0b5e01f48de62d7ccea0abea046e2
a496

References

  • bitcoin-dev: Original mailing list thread
  • BIP-0174: Partially Signed Bitcoin Transaction Format
  • BIP-0322: Generic Signed Message Format from March 25th 2020