Skip to content

Instantly share code, notes, and snippets.

@imaginaryusername
Last active June 3, 2019 08:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save imaginaryusername/0b24462b8b564b51541ffe5ec02be8d6 to your computer and use it in GitHub Desktop.
Save imaginaryusername/0b24462b8b564b51541ffe5ec02be8d6 to your computer and use it in GitHub Desktop.

##BCH reusable address

discussion sketch 20190602

@im_uname, with material from @marklundeberg and discussion with @cpacia

Goals in this version

Funds sent in one transaction - no setup (unless offline - see later sections)

Transaction indistinguishable from "normal" p2pkh/p2sh-multisig transactions

Funds in the entire wallet recoverable by only seed

Can receive from P2PKH or P2SH-multisig addresses

Two versions - P2PKH or P2SH-multisig wallets

Ability for addresses to "expire" and adjust resource usage after a long period

Schnorr-multisig compatibility (TBD)

Leave space for offline communications channels (prefixless, but backup to file instead of seed)

Paycode format

For recipient who intend to receive in p2pkh addresses, encode the following in bech32 using the same character set as cashaddr:

Field Size Description Data Type Comments
1 version uint8 paycode version byte; 1 for p2pkh. Set the first bit to 1 to indicate offline-communication only.
1 suffix_size uint8 length of the filtering suffix desired in bytes; 0 if no-filter for full-node or offline-communcations
32 scan_pubkey char ECDSA/Schnorr public key of the recipient used to derive common secret
32 spend_pubkey char ECDSA/Schnorr public key of the recipient used to derive common secret
4 expiry uint32 UNIX time beyond which the paycode should not be used. 0 for never
? checksum char error-correction (TBD)

For recipient who intend to receive in p2sh-multisig addresses, encode the following in bech32 using the same character set as cashaddr:

Field Size Description Data Type Comments
1 version uint8 paycode version byte; 2 for p2sh-multisig. Set the first bit to 1 to indicate offline-communication only.
1 suffix_size uint8 length of the filtering suffix desired in bytes; 0 if no-filter for full-node or offline-communcations
1 multisig_setup uint8 instruction on constructing the multisig m-of-n to be paid to. first four bits indicate m parties who can recover funds, while last four bits indicate n parties total. Neither can be zero, m <= n.
32 scan_pubkey char ECDSA/Schnorr public key of the recipient used to derive common secret
32 spend_pubkey1 char First ECDSA/Schnorr public key of the recipients
32 spend_pubkey2 char Second ECDSA/Schnorr public key of the recipients
... ... ... ...
32 spend_pubkeyn char nth ECDSA/Schnorr public key of the recipients
4 expiry uint32 UNIX time beyond which the paycode should not be used. 0 for never
? checksum char error-correction (TBD)

The payment code shall be prefixed with paycode:, and can be suffixed with offchain communications networks it supports in URI, e.g. ?xmpp=johndoe@something.org&matrix=@john123:something.com.

Disclaimer

This section is intentionally left vague to avoid any pretense that I know the intricacies of the cryptography involved - @im_uname

Paycode creation from two keypairs (P2PKH)

Obtain two ECDSA/Schnorr keypairs from wallet, and designate one as the "scanning" pubkey while the other as the "spending" pubkey. In order to reduce client-side resource usage, the wallet can designate a scanning server and reveal the scanning private key to the server. This server will have to be trusted with the wallet's privacy as it will know all its receiving transactions; alternatively the wallet can simply reveal its desired prefix to the server, and do the scanning itself.

Any common-secret-derived keypairs detected is additionally stored in the wallet file for history and spending, to avoid additional resource usage.

Wallet creation (P2SH-multisig)

The multisig m-of-n parties do not keep transaction scanning privacy from each other, and must agree on a common scanning keypair. They can subsequently submit one ECDSA/Schnorr spending pubkey each, and set up the paycode using the n+1 public keys. In order to reduce client-side resource usage, the wallets can designate a scanning server and reveal the scanning private key to the server. This server will have to be trusted with the wallets' privacy as it will know all its receiving transactions; alternatively the wallets can simply reveal its desired prefix to the server, and do the scanning themselves.

Sending

Sender's wallet shall first check the expiry time embedded in the paycode is at least one week ahead of local clock (skip if expiry time is 0). If expiry time is more than a week ahead, proceed.

Sender's wallet shall determine the outpoints she wants to spend from, and determine which input is at position 0.

In the case of paying from P2PKH, P below is simply the public key of the first input. In the case of paying from P2SH-multisig, it is the first public key with a valid signature.

A common secret c can be derived as follows:

Q = scan_pubkey

d = scan_privkey

R = spend_pubkey

f = spend_privkey

Q = dG

R = fG

P = eG = first public key embedded in input 0 with a valid signature

s = integer derived from outpoint spent by first input

Common secret c = H(H(eQ) + s) = H(H(dP) + s)

Pay to address from key R' = R + cG

To recap, we use the first pubkey with a valid signature of the transaction's first input, together with the scan key, to derive a shared secret via Diffie Hellman. This shared secret is combined with the outpoint to obtain a unique scalar value for this payment, that is used to tweak the spend key into a unique ephemeral key.

And then, use different nonces to sign the first input until the last suffix_size bytes of the signature is shared with the scan_pubkey (skip if suffix_size = 0), then broadcast the transaction.

Sending to P2SH-Multisig

In the case of paying from P2PKH, P below is also the public key of the first input. In the case of paying from P2SH-multisig, it is the first public key with a valid signature.

The different part is all of the spending public keys will have to be involved in constructing a new P2SH address, described below in paying to a two-of-three P2SH-multisig setup:

Common secret c can be derived as follows:

Q = scan_pubkey

d = scan_privkey

R1, R2, R3 = spend_pubkey

f1, f2, f3 = spend_privkey

Q = dG

R1 = f1G, ...

P = eG = first public key embedded in input 0 with a valid signature

s = integer derived from outpoint spent by first input

Common secret c = H(H(eQ) + s) = H(H(dP) + s)

Pay to a new P2SH address constructed from keys R1' = R1 + cG, R2' = R2 + cG and R3' = R3 + cG, with m of n specified in OP_CHECKMULTISIG script.

Like the case of sending to P2PKH, the sender uses different nonces to sign the first input until the last suffix_size bytes of the signature is shared with the scan_pubkey (skip if suffix_size = 0), then broadcast the transaction.

Receiving

We assume two parties exist: A scanning public node, and the actual spending wallet. They may be run by the same person, in which case suffix-filtering becomes unnecessary.

The scanning node shall index all transactions from the inception date of reusable addresses by the suffix of their first input signature (or, in the case the first input is P2SH-multisig, first signature of the first input). Ignore transactions where the first input is neither P2PKH nor P2SH-multisig.

Upon receiving a subscription with a given paycode, the scanning node look for transactions whose first signature matches suffix indicated by suffix_size and scan_pubkey that happened after wallet creation date or last scanning date, but before the expiry date embedded in the paycode, and forwards them to the wallet.

If additional filtering is desired where the wallet trusts the scanning node with its privacy, the wallet can also forward its scan_privkey to the node; the node can then derive common secret c from scan_privkey, outpoint spent by the first input and the first public key from the first input of each suffixed transaction, and only forward transactions where addresses match R' = R + cG. Otherwise the wallet shall perform the additional filtering locally.

Upon obtaining transactions filtered by the scan_privkey, the receiving wallet then stores it locally and assign a spending keypair R' and h = (f+c) to it.

Receiving to P2SH-Multisig

The scanning and filtering part shall work exactly like in P2PKH. Once the multisig parties have a filtered transaction ascertained by the scan_privkey, the receiving parties can then each assign public keys R1', R2'... and private keys f1+c, f2+c...to them. With these keysets the address can be spent from normally.

Expiration time

A suffixed, non-delegating stealth wallet as described above, as long as it does not delegate scanning to a full node, will require a fixed fraction of total bitcoin cash bandwidth to run. While the consumption does not have to be latency sensitive in the context of a light wallet, it is vulnerable to long term total traffic fluctuation. If the suffix is too short while network traffic gets much higher, the wallet might have difficulty running as bandwidth requirement rises with network traffic. On the other hand, if the suffix is too long when network traffic is low, the wallet's privacy is degraded as its anonymity set shrinks.

In order to mitigate this, an optional expiry time can be added; sending wallets shall respect the expiry time by yielding an error if attempting to send past it - any funds that are sent overriding the expiry is considered lost, and the recipient has done due diligience warning sender in the paycode.

When scanning, nodes or wallets can allow for a certain amount of buffer beyond the expiry date, in case a transaction is sent before but remain unconfirmed beyond the expiry - in the context of BCH, a week might be more than sufficient.

Receiving via offline communication channels in lieu of suffixing (optional)

If an offline communications channel can be established, no filtering of any sort is necessary. Instead, the sender can follow the following procedure:

  1. Send the amount plus necessary fees to an address on the change path, and immediately mark the output address; in the case of wallet data loss it can still be recovered from seed, but if a timeout occured, sender shall move the output to yet another address to prevent recipient from recovering it unexpectedly in the future. To preserve privacy and deter blockchain analysis, sender can optionally send excess amounts of coins to this address, at the cost of having the excess amount held frozen until either recipient claim or timeout.

  2. Construct a signed transaction sending required amount to reusable addresses (P2PKH or P2SH) as described in sections above, except the suffix-grinding part as recipients would not need to filter. Encrypt the transaction against the recipient's scanning pubkey, and send through the recipient's specified communication channel along with a timeout.

  3. Upon receipt, the recipient decrypt the transaction and broadcast it. To preserve ability to restore from seed, they should follow up by moving funds to an address on a regular HD wallet they control - note that funds are vulnerable to data loss as long as it stays on the reusable address, the recipient cannot retrieve it unless they perform scanning over the full blockchain. The sender might aid in recovery if they have backups of the sent transaction.

Considerations

Anonymity set for suffix_size 0 is effectively all transactions with p2pkh outputs (TBD p2sh-multisig); with suffix_size > 0, it is reduced by a factor of 1/(256)^(suffix_size) at each step.

TODO: Security assumptions

TODO: Security assumptions (P2SH-multisig)

TODO: Resource consumption

References

Chatlogs with @marklundeberg

BIP-Stealth https://github.com/genjix/bips/blob/master/bip-stealth.mediawiki

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