Skip to content

Instantly share code, notes, and snippets.

@markblundeberg
Last active June 1, 2019 15:20
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save markblundeberg/bd28871548108fc66d958018b1bde085 to your computer and use it in GitHub Desktop.
Save markblundeberg/bd28871548108fc66d958018b1bde085 to your computer and use it in GitHub Desktop.
"Pay To Identity" — a proposed use of OP_CHECKDATASIG

"Pay To Identity" — a proposed use of OP_CHECKDATASIG

Dr. Mark B. Lundeberg, 2018 September 6 bitcoincash:qqy9myvyt7qffgye5a2mn2vn8ry95qm6asy40ptgx2

A mechanism where a Bitcoin Cash payment is made to a personally identifying string (real name, email address, social media handle, etc.) instead of directly to a cryptographic key. The payment can only be claimed by the recipient if they generate a public key and get it certified by an identity verifier. This certification signature is confirmed in script via the new opcode OP_CHECKDATASIG.

Characteristics:

  • Pay anyone, right now -- recipient doesn't need to have any cryptographic keys nor do they even need a phone/computer. (They only need these to claim the funds later.)
  • Minimal setup -- sender needs to have a wallet plugin that knows the public key of one or more identity verifiers. The recipient needs no setup, nor do the sender/recipient need to know each other ahead of time.
  • Non-interactive payment -- the sender pays to a P2SH address and then sends a (possibly unencrypted) message to recipient. No further interaction required between sender and recipient.
  • Non-custodial -- funds are not held by trusted third party; the third party involved (identity verifier) does not even know a payment is occurring.
  • Reasonably secure -- about as secure as an identity verification system can be.

Anticipated use cases: Onboarding new users, Tipping on social media.

Contents

Background

Currently, there are two methods by which you can non-interactively send a BCH payment to someone who doesn't have a wallet or whose bitcoin address is unknown. Each of these methods has limitations:

  • Give private key (paper wallets, etc.): A traditional method of giving bitcoin to new users is to hand them a private key that is loaded with a certain amount of bitcoin. Technically this requires more trust on the part of the recipient since the sender may keep a copy of the private key. Practically speaking, new users are not typically aware of this distinction. The transmission does create a concern for the sender, who may worry about eavesdroppers who can trivially steal the funds.
  • Custodial deposit service (tip bots, etc.): The tipping bots u/chaintip and u/tippr on reddit allow you to tip BCH to any reddit user. The funds are held by the bot, and then afterwards dispersed according to the user's request. The fact that funds are custodially held by the bot may raise concerns about the ultimate degree of trust placed in the bot. It could also be a legally murky practice, in terms of money transmitter regulations.

This document is the result of a discussion with Jonathan Toomim, who realized it may be possible to use my contrived PGP/OP_CHECKDATASIG interaction to allow a 'pay to identity' mode, that solves some of the problems of the aforementioned approaches.

Protocol outline

Parties

  • Alice, who is sending funds.
  • Bob, the recipient.
  • Tom, a known identity verification service.

Setup

Alice needs to have a wallet with an appropriate plugin for generating pay-to-identity scripts. She also needs to know the public key of Tom.

Bob doesn't need any setup.

Procedure (Alice pays Bob)

  1. Alice determines a string that identifies Bob, picks a third party Tom that can verify Bob is identified with this string, and then creates a funding transaction with a P2SH output for the redeem script shown below.
  2. Before broadcasting the funding, Alice makes sure to save necessary recovery information in case of failure. She then broadcasts the funding transaction.
  3. Alice sends a message to Bob telling him that txout XXXXX:N with redeemscript RRRRR is waiting from him to unlock, and instructions on what to do to unlock payment.
  4. Bob installs a BCH wallet and any necessary plugin for claiming the redeem script.
  5. Bob contacts Tom in order to verify his identity, and follows whatever steps Tom deems are necessary.
  6. Bob asks Tom to sign a packet that binds his identity to a public key of his choice. Tom certifies by signing the packet with his secp256k1 key.
  7. Bob then constructs a spend transaction using the redeem script, the identity-pubkey packet and its certification signature, and a transaction signature from his key.

In practice, these technical operations should of course be hidden behind friendlier user interfaces. I suggest two variants of the identity certification packet: Basic, and PGP.

Pay-to-identity (basic version)

The identity of Bob --- byte string id --- can be specified in a variety of ways, depending on the preferences of the identity verifier. One possibility is to use URI-style syntax "name:Bob Bobberson", "mailto:bob@example.com", "mailto:Bob Bobberson <bob@example.com>" "facebook:bob_bobberson", "reddit:xXxCoolBobxXx". Another possibility is to use LDAP-style Distinguished Names: "cn=Bob Bobberson,o=example.com". Note that in each case, a different identity verification method would be appropriate.

Alice can optionally choose to hide Bob's identity from being recorded on the blockchain, for privacy reasons. She takes id = "hidden:"|HASH160(salt | identity), where identity is Bob's plaintext identity and salt is a random value. Alice includes the salt value in her message to Bob. When requesting certification on such a hidden identity, Bob must inform the identity verifier of salt and identity values. For reasons of avoiding certain length manipulation attacks, the verifier should require that salt is of a fixed length (suggested: 32 bytes). Only Alice, Bob, and the identity verifier need to know the value of salt, whereas all other observers will only see the obfuscated id on the blockchain. It is impossible to associate id with Bob's identity, without knowing salt.

The identity-pubkey binding packet is encoded as packet = HASH256(pubkey)|id, and the digest SHA256(packet) is what gets signed by the certifier. The usage of HASH256(pubkey) instead of just pubkey here is done since the verifier does not need to know pubkey. As far as the verifier is concerned, the first 32 bytes of the packet are just arbitary user-supplied data that may or may not have anything to do with blockchain or payments.

Script

To build a redeem script Alice needs:

  • recovery_pubkey and recovery_time set by Alice. 33 bytes and 5 bytes, respectively.
  • tpubkey, the public key Tom, the identity verifier. 33 bytes if compressed.
  • id, the string containing recipient's identity (plain or hidden). Variable number of bytes.

To spend, Bob will need the following in the scriptSig:

  • certsig, the ECDSA signature from identity verifier's tpubkey that certifies the identity-pubkey packet.
  • bpubkey, his own public key.
  • txsig, the signature from bpubkey on the spend transaction.

Redeem script (parentheses show stack state after each operation when Bob redeems it with a valid scriptSig):

(pushes from scriptsig:  certsig, bpubkey, txsig, 0)
IF                      (certsig, bpubkey, txsig)
  <recovery_pubkey>     -skip-
  CHECKSIGVERIFY        -skip-
  <recovery_time>       -skip-
  CHECKLOCKTIMEVERIFY   -skip-
ELSE
  OVER                  (certsig, bpubkey, txsig, bpubkey)
  CHECKSIGVERIFY        (certsig, bpubkey,)
  SHA256                (certsig, SHA256(bpubkey),)
  <id>                  (certsig, SHA256(bpubkey), id)
  CAT                   (certsig, SHA256(bpubkey)|id)
  <tpubkey>             (certsig, SHA256(bpubkey)|id, tpubkey)
  CHECKDATASIG          (1)
ENDIF

Alice may also redeem this script after recovery_time, by pushing a scriptSig (rtxsig, 1) with a transaction signature from the recovery key.

Pay-to-identity (PGP version)

In a more advanced formulation, Bob's identity packet can take the form of a PGP key certification. As demonstrated in a previous gist, this is possible since modern PGP supports secp256k1 ECDSA keys and signatures, and the certification algorithm can be made compatible with OP_CHECKDATASIG. Below the packet format is demonstrated by example. In effect this operates like the basic mode above, but the PGP "uid" string takes the place of id and the public key appears in an unhashed form.

In order to redeem, Bob must also install PGP software; the certification he receives from the identity verifier, Tom, is an ordinary PGP key certification that can be created with gpg --sign-key.

Example data

Identity verifier: 873ECC6A

  • pubkey = 04A788D320086C086C71E15D34E62A2308240AFCCC2325BB34F07EDF56706F62A60330EA3E1DF9EE826AFA2EB4F382D681C99F27BCCC486A9390D65B305215F978
  • pubkey (compressed) = 02A788D320086C086C71E15D34E62A2308240AFCCC2325BB34F07EDF56706F62A6
  • same key as used in my previous gist.

Recipient key: 459504C5

  • pubkey = 04CB987E4742B31CF7B7B9DE0784E1E5C6081AA91009B7E9B41EC2F76D95024DCB3F45FE3563B33FCDE6F5A7C82738BB18498CA15D73FF4C5AB3F30D26EB225B8E
  • pubkey (compressed) not relevant; it will not be used.
  • UID = "Bobby <bob@example.com>".
  • The above key+UID combination has been certified by the identity verifier key.

In the case of PGP, the identity-pubkey binding packet is the PGP certification preimage, msg, shown here in hexadecimal:

99004f045b89b5ea13052b8104000a020304cb987e4742b31cf7b7b9de07
84e1e5c6081aa91009b7e9b41ec2f76d95024dcb3f45fe3563b33fcde6f5
a7c82738bb18498ca15d73ff4c5ab3f30d26eb225b8eb400000017426f62
6279203c626f62406578616d706c652e636f6d3e04101308000605025b89
b5ff04ff0000000c

The digest SHA256(msg) = 2f585f...8bd6 is signed to create the certification signature.

Breaking msg into parts -- see RFC4880 for more information:

  • msgA = 99004f (packet header for upcoming public key packet, packet length 79)
  • msgB = 045b89b5ea13052b8104000a (start of public key packet v4 with ECDSA: creation time, algo, secp256k1 OID)
  • msgC = 04cb987e4742b31cf7b7b9de0784e1e5c6081aa91009b7e9b41ec2f76d95024dcb3f45fe3563b33fcde6f5a7c82738bb18498ca15d73ff4c5ab3f30d26eb225b8e (secp256k1 public key, uncompressed)
  • msgD = b400000017 (packet header for upcoming UID packet, idiosyncratically using 32-bit length)
  • msgE = 426f626279203c626f62406578616d706c652e636f6d3e (UID: "Bobby <bob@example.com>")
  • msgF = 04101308000605025b89b5ff (hashed part of certification packet, includes certification time)
  • msgG = 04ff0000000c (final append to hashed data, includes length of msgF)

In what follows we will use things like msgAB to refer to the concatenation of msgA and msgB; note that the full msg is equivalently msgABCDEFG.

Script

To build a redeem script Alice needs:

  • recovery_pubkey and recovery_time set by Alice. 33 bytes and 5 bytes, respectively.
  • tpubkey, the public key of the identity verifier. 33 bytes.
  • msgDE, the PGP UID packet containing required recipient's identity. Variable number of bytes.

To spend, Bob will need the following in the scriptSig:

  • certsig, the ECDSA signature from tpubkey that verifies the identity. Bob obtains this by converting the PGP signature to bitcoin's encoding.
  • msgAB, msgFG as above -- these are extra PGP prefix/suffix that are not used in the bitcoin script aside from being used to reconstruct the full signed msg.
  • msgC, his public key.
  • txsig, the signature from msgC on the transaction, which allows Bob to control where the funds go.

Redeem script (parentheses show stack state after each operation when script is run in payment mode):

(pushes from scriptsig:  certsig, msgAB, msgFG, msgC, txsig, 0)
IF                      (certsig, msgAB, msgFG, msgC, txsig,)
  <recovery_pubkey>     -skip
  CHECKSIGVERIFY        -skip-
  <recovery_time>       -skip-
  CHECKLOCKTIMEVERIFY   -skip-
ELSE
  OVER                  (certsig, msgAB, msgFG, msgC, txsig, msgC)
  CHECKSIGVERIFY        (certsig, msgAB, msgFG, msgC,)
  <msgDE>               (certsig, msgAB, msgFG, msgC, msgDE)
  CAT                   (certsig, msgAB, msgFG, msgCDE)
  SWAP                  (certsig, msgAB, msgCDE, msgFG)
  CAT                   (certsig, msgAB, msgCDEFG)
  CAT                   (certsig, msgABCDEFG)
  <tpubkey>             (certsig, msgABCDEFG, tpubkey)
  CHECKDATASIG          (1)
ENDIF

It may be desirable to separate the PGP private key of Bob from his bitcoin (transaction-signing) private key. This would require a slight alteration to the above script, involving an additional CHECKDATASIG operation and more scriptsig inputs.

For improved security it may be desirable to alter this script slightly: the redeem script can ensure msgAB is always 15 bytes long and even hard-code part of it (11 bytes are the same, only the 4 bytes expressing creation time vary). Likewise, the first 4 bytes of msgFG can be hard-coded to ensure that the signature is actually a key certification, and the length of msgF should be checked against the length stated in msgG. These steps would prevent a form of attack where Tom naively signs a binary document that is carefully crafted to partly mimic a key certification. In any case, Tom should not be using a certification key in such a manner.

Security

The importance of some trust in the third party Tom is demonstrated by considering a few avenues of attack:

  • Alice and Tom could collude to steal the payment from Bob.
  • If Alice's message to Bob is intercepted by Eve, then Eve can collude with Tom to spend.
  • Even if Tom is honest, Alice or Eve may attempt to trick Tom via one of many identity theft techniques.

Curiously, Tom cannot easily steal the payment on his own, since he does not know the redeem script and Bob does not tell it to him. At the time when he is certifying, only the redeem script hash has appeared on the blockchain, and he cannot feasibly guess its preimage since it includes a random recovery_pubkey that should have never been used before. The earliest time that Tom can learn of the redeemscript will be when Bob finally broadcasts his redemption transaction. Tom would have to react immediately and execute a double spend attack in order to steal the funds.

Extensions

Use of PGP keyservers for subsequent payments

The steps of Pay-to-identity are rather complicated, especially the burden placed on Bob during the redemption process. For subsequent payments where Bob does have a wallet, the process may be simpler if Bob places his PGP key (and certification from Tom) on a public PGP key server. These options allow Alice to more directly make a non-interactive payment to Bob:

  • Alice can pay directly to a P2PKH address using Bob's secp256k1 key, or, Bob may add a "bitcoincash:" address as a separate self-signed UID.
  • If Bob desires more privacy, he can place a stealth address or reusable payment code as a separate self-signed UID.

This approach also adds more flexibility as it allows Alice to use full PGP feature set for determining Bob's identity (web of trust, key revocations, etc.). If Bob additionally has encryption keys and has a UID indicating a preferred contact point, this allows Alice to make even stealthier payments using encrypted notifications instead of blockchain notifications.

Multi-sig certification

It may be desirable to have multiple identity verifiers for added security and/or redundancy. Although a multi-signature variant does not exist for CHECKDATASIG, it is possible to emulate this behaviour using script. For example, a 2-of-3 multisig may be done as follows (stack shows example execution with msg signed by pubA and pubC but not signed by pubB):

... push signatures ...
... prepage msg ... (sigC, 1, 0, sigA, 1, msg) ()
0                   (sigC, 1, 0, sigA, 1, msg, 0) (0)
TOALTSTACK          (sigC, 1, 0, sigA, 1, msg) (0)
SWAP                (sigC, 1, 0, sigA, msg, 1) (0)
IF                  (sigC, 1, 0, sigA, msg) (0)
  TUCK              (sigC, 1, 0, msg, sigA, msg) (0)
  <pubA>            (sigC, 1, 0, msg, sigA, msg, pubA) (0)
  CHECKDATASIGVERIFY(sigC, 1, 0, msg) (0)
  FROMALTSTACK      (sigC, 1, 0, msg, 0) ()
  1ADD              (sigC, 1, 0, msg, 1) ()
  TOALTSTACK        (sigC, 1, 0, msg) (1)
ENDIF
SWAP                (sigC, 1, msg, 0) (1)
IF                  (sigC, 1, msg) (1)
  TUCK
  <pubB>
  CHECKDATASIGVERIFY
  FROMALTSTACK
  1ADD
  TOALTSTACK
ENDIF
SWAP                (sigC, msg, 1) (1)
IF
  TUCK              (msg, sigC, msg) (1)
  <pubC>            (msg, sigC, msg, pubC) (1)
  CHECKDATASIGVERIFY(msg) (1)
  FROMALTSTACK      (msg, 1) ()
  1ADD              (msg, 2) ()
  TOALTSTACK        (msg) (2)
ENDIF
DROP                () (2)
FROMALTSTACK        (2) ()
2                   (2, 2) ()
EQUALVERIFY         () ()

This adds a length to the redeem script of 6 bytes plus 42 bytes per pubkey (if compressed), whereas standard multisig is ~2 bytes plus 34 bytes per pubkey (if compressed).

Pay in dollars

Due to the large volatility in BCH value, it is desirable to be able to fix a payment amount referenced in a less volatile currency such as the US dollar. This is useful since Alice may not know at what time Bob will claim the payment, but she wants to make sure that the dollar value is correct at the time he claims.

It is beyond the scope of this document to go into detail, but as a rough sketch, this can be accomplished by extending the above scripts to require a further transaction signature by a fourth party. The fourth party would use metadata in the redeemscript to determine the correct payment amount to Bob, the correct change to Alice, and the correct refund address for Alice, and then only sign and broadcast Bob's claim transaction if the outputs match the expected values.

(Unfortunately it does not currently seem practical to use public 'price oracles' and CHECKDATASIG to accomplish this, for a number of reasons.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment