BIP: ?? Layer: Applications Title: SNICKER - Simple Non-Interactive Coinjoin with Keys for Encryption Reused Author: Adam Gibson <[email protected]> Comments-Summary: No comments yet. Comments-URI: - Status: Proposed Type: Informational Created: - License: BSD-2-Clause
SNICKER (Simple Non-Interactive Coinjoin with Keys for Encryption Reused) is a simple method for allowing the creation of a two party coinjoin without any synchronisation or interaction between the participants. It relies on the idea of reused keys (either reused addresses, or identification of ownership of signed inputs and thus pubkeys in signatures). The address reuse scenario may mean that this is suitable as a privacy repair mechanism, but it would likely be used more broadly as a method for opportunistic coinjoin requiring literally zero user (inter)action within wallets. The implementation requirements for wallet developers are minimal.
For a discursive treatment of the idea, see the original blog post[1] (although any conflicting details are superseded by this document).
The purpose of this document is to standardise those features of the protocol which could be shared across wallets.
This BIP is licensed under the 2-clause BSD license.
Existing use of CoinJoin [2] is very valuable in creating sets of utxos in an obfuscated/mixed state, leveraging to the maximum what has sometimes been called the "intrinsic fungibility" of Bitcoin.
As well as past systems such as SharedCoin[3] and DarkWallet[4], we have, as of writing in mid-2019, JoinMarket[5], Wasabi Wallet[6] and Samourai's[7] tools Whirlpool and Stowaway. Coinshuffle[8] is an example of an arguably more powerful cryptographic protocol to achieve similar goals, although as of writing is not in production use on Bitcoin.
The most difficult thing about these CoinJoin protocols and systems is that they require coordination between participants, which can be difficult in any case, but is *especially* difficult when we want to ensure a strong level of anonymity for the participants. A logical way to ameliorate this problem is to have a coordinating server to create CoinJoin transactions, but this is a tradeoff which degrades the privacy guarantees of the user (even if very subtly), and can also be fragile to attack (central point of failure). Giving any detailed information to a server over a network connection is problematic, even when those connections are over an anonymising network like Tor.
On the other hand with no controlling central party, Sybil attacks can be quite effective in damaging the viability of such protocols in various ways - jamming the protocol, snooping the network level, Sybilling to swamp the participant and break or severely damage the privacy boost he was trying to achieve, or in cases like Joinmarket, damaging the economic incentive model.
SNICKER tries to occupy a radically different position in the landscape of tradeoffs here. No coordination or synchronisation is needed between users, all that is needed is one side to broadcast encrypted data, and the other side to receive it. The reception could happen over radio or satellite, so that it's basically physically impossible for anyone to know what happened (this is not a suggestion; just illustrating how important the broadcast element is). The SNICKER coinjoin model is very limited, as laid out in this document (2 party only!), but it's considered that for some scenarios, this is a worthwhile tradeoff because all of the coordination issues are no longer applicable.
The basic idea is for one party, using information only from the blockchain, to create a partially signed coinjoin transaction, deducing an output for a second party without their involvement, then encrypting that proposal and broadcasting it to the world in some way. Note the proposal is for a 2 party coinjoin (more parties is theoretically possible but much more complex). Such proposals can be constructed based on making inferences from public keys seen on the blockchain - either from address reuse or simply extracted from transaction inputs. Notice that multiple proposals with the same input utxos is not problematic. Whether such proposals will be taken up (i.e. downloaded from some server, decrypted, co signed and broadcast onto the Bitcoin network) will depend on whether the proposal is valid (i.e. whether keys were correctly identified), whether the counterparty is aware of the proposal, and whether they are willing to do the coinjoin. For the latter, economic incentives are relevant.
For the former of the two points just mentioned, it'll be important for the first party (the "Proposer") to find candidates with some reasonable (even if low) probability. This could be bootstrapped by choosing on-chain keys participating in transactions with certain flags (for example: Joinmarket transactions). Over time, this could be supplanted by searching for transactions which themselves are already SNICKER (hence "bootstrap" - after some time SNICKER txs could spread into a large graph).
We will see in the Specification below, that there are two ways proposed for making this identification - address reuse or inference of co-ownership of pubkeys in transaction inputs. It is worthy of note here that another option without address reuse will exist if and when Taproot[9] is activated on the Bitcoin network, since in that case the scriptPubKey will expose the pubkey directly.
Here we seek to standardise certain features, to make it feasible for a wallet software developer to create an implementation of SNICKER that is compatible with other wallets.
It is noted however that due to the scanning/searching nature of the behaviour of the Proposer, it may be that wallet developers would find focusing only on the Receiver side of the specification is more practical (or, occasionally, vice versa).
- Proposer - scans the blockchain for relevant information, then creates a partially signed coinjoin transaction and publishes an encrypted version of it on the bulletin board.
- Receiver - discovers the encrypted version of the partially signed coinjoin transaction on the bulletin board (or otherwise) and decrypts it, co-signs it (if desirable) and broadcasts it onto the Bitcoin network.
- Bulletin board - Any public read/writable location, ideally publishing to it and reading from it should be anonymous, so a good example is a Tor hidden service.
c
- the tweak used to generate a new destination for the Receiver (32 byte group element, encoded as for private keys).C
- ciphertextECDH(P, Q)
- outputs a 32 byte shared secret between owners of P and Q, defined the following operations:- given a secp256k1 pubkey
P
with known privkey p and a second secp256k1 pubkeyQ
: - perform scalar multiplication to derive a new pubkey:
p * Q
- serialize this pubkey as for Bitcoin (so, 02 or 03 are prepended to the x-coordinate and the complete serialization is exactly 33 bytes)
- Calculate the 32 byte **shared secret**
S
as:S = SHA256(serialized pubkey from previous step)
- Note that this process is that followed by the libsecp256k1 ECDH module[10]
- given a secp256k1 pubkey
ECIES-E(P, m)
- outputs an encryption of messagem
to secp256k1 public keyP
, defined by the following operations[11]:- construct a new secp256k1 pubkey R from a newly generated privkey r.
- perform scalar multiplication to derive a new pubkey
S
:S = r * P
- serialize both these pubkeys as for Bitcoin (so, 02 or 03 are prepended to the x-coordinate and the complete serialization is exactly 33 bytes). Call these
R-ser, S-ser
. - calculate
K = SHA512(S-ser)
- Set
IV
equal to the first 16 bytes ofK
. SetK-AES
to the second 16 bytes ofK
. SetK-MAC
to the last 32 bytes ofK
. - Perform AES128 encryption of the message
m
with IVIV
and encryption keyK-AES
. Use PKCS 7 as the padding method and CBC as the block cipher mode. Let the ciphertext output beC = AES128-CBC(IV, K-AES, m)
. - Calculate a MAC using HMAC-SHA256 with the key
K-MAC
to findmac = HMAC-SHA256(K-MAC, C)
. - Concatenate to find the output bytestring, first prepending 4 magic bytes
BIE1
or0x42494531
:o = 4 magic bytes || R-ser || C || mac
. - Finally, the output can optionally be base64 encoded for transfer. Note that this algorithm, including the specific magic bytes, conforms to that used currently by Electrum[12].
ECIES-D(p, o)
- outputs the decryption of an ECIES outputo
, assuming it had been encrypted to the pubkeyP
for whichp
(which the decryptor must possess) is the privkey.- base64 decode
o
if necessary. - check that the decoded
o
has first four bytes0x42494531
and reject otherwise. - deserialize bytes 5 to 37 into a secp256k1 pubkey object
R
; reject if this operation fails. - perform scalar multiplication to derive a new pubkey
S
:S = p * R
- serialize
S
andR
as for Bitcoin (so, 02 or 03 are prepended to the x-coordinate and the complete serialization is exactly 33 bytes). Call theseR-ser, S-ser
. - calculate
K = SHA512(S-ser)
- Set
IV
equal to the first 16 bytes ofK
. SetK-AES
to the second 16 bytes ofK
. SetK-MAC
to last 32 bytes ofK
. - split the remaining bytes of
o
into two sections: the last 32 bytes and the bytes from byte 38. The first is calledmac
and the second is calledC
(ciphertext). - First check that
mac
is a valid mac onC
with the keyK-MAC
:mac ?= HMAC-SHA256(K-MAC, C)
. Reject if false. - Perform AES128 decryption of the ciphertext
C
with IVIV
and decryption keyK-AES
. Use PKCS 7 as the padding method and CBC as the block cipher mode. The final plaintext output of decryption,m
, is then:m = AES128-CBC(IV, K-AES, C)
.
- base64 decode
The Proposer will need to identify a set of addresses which are reused (specifically, has an unspent output currently existing, and previously spent from at least once). This could be global or restricted in some way (e.g. recency of usage, specific wallet type etc.), and may be found via direct blockchain scanning.
For each reused address A
, he will find the public key P_A
from the/a previous spend. He will then take these steps:
- Find one and only one utxo of his own to spend, whose bitcoin value is greater than or equal to: value of utxo owned by
A
, plus approximately the transaction fee for a 2-in 3-out transaction. - Construct a new tweak c (see "Terms" above), using specifically this method (The reason for this specific method will be made clear here):
- First, take the public key of the utxo input chosen in the previous step, call it
Q
, and its corresponding private keyq
. - Calculate
c = ECDH(Q, P_A)
- First, take the public key of the utxo input chosen in the previous step, call it
- Construct a new destination address using pubkey
P_A + cG
and of the same address type asA
. - Construct a transaction with inputs: (existing utxo owned by
A
, his own utxo owned byQ
) and outputs: (P_A + cG
, his own freshly generated output addressesO1
andO2
). The amounts will be such that each party (Proposer, Receiver) receives back approximately what they put into the transaction, but the acceptability of the amount is for the Receiver to decide.O1
MUST receive exactly the same amount asP_A+cG
andO2
should contain the change required to balance for the Proposer. - He signs the transaction for his own input, so it is partially signed.
- He serializes the partially signed transaction and the tweak value
c
according to the serialization specification below, calling this serializationM
, he outputs and publishes a **Proposal**:ECIES-E(P_A, M)
. - He publishes this Proposal (as according to definitions, it should be base64 encoded) to the Bulletin board.
The proposer constructs the message m
before encryption as follows:
- 7 bytes: magic, binary 'SNICKER' i.e.
0x534e49434b4552
- 2 bytes: version, specifically:
- First byte is a version, currently only 00 and 01 are defined (see below for Version 01). So here this MUST be the byte 0x00.
- Second byte represents a set of flags. For version 00 and 01 no flags are defined and so this value MUST be
0x00
.
- 32 bytes: tweak value
c
calculated as explained above. This MUST be a valid element of the additive group of integers modulo N (where N is secp256k1's N), i.e. it must be a valid raw private key in the Bitcoin sense. The encoding must be a big endian serialization of the 256 bit integer. Note this must be fixed width i.e. always 32 bytes. - Variable size: A partially signed bitcoin transaction according to the specifications of PSBT, as specified in BIP174[13]. Note that this should be the binary serialized variant and not the base64 encoded variant. See next subsection on rules for the content of this PSBT.
The Proposer can be flexible in some aspects of creation of the transaction proposal above, but these conditions must be met for BOTH Version 00 and Version 01, note that the list may substantially differ for other versions. Note that explicit choices are always preferred here, since multiple implementations could otherwise risk creating watermarks from individual Proposers.
- (see subsection 'A note on address types' below). At least one output MUST have scriptPubkey: for Version 00, (address type as for
P
) using single public key:P + cG
- Transaction version MUST be 02
- Transaction locktime MUST be 0, and sequence numbers MUST be
0xffffffff
. Opt-in RBF[14] is probably not practical in this type of protocol, so the proposal here requires using the simplest (default) values. - There MUST be AT LEAST TWO outputs with the exact same output value in satoshis. One of those outputs must be the one to the Receiver as specified in item 1 above. Another must be of the same scriptPubKey type.
- There MUST be ONLY ONE input that the Receiver currently owns. This must be the/a utxo that is controlled by (address version of P)
P
. - The Bitcoin transaction fee applied to the proposed transaction is not restricted; the Receiver will decide if it is acceptable or not.
- The AT LEAST ONE input NOT owned by the Receiver (see above) MUST be finalized as per the terminology of BIP174, that is to say, it/they must have fields 0x07 finalized scriptSig and/or 0x08 finalized scriptWitness completed and valid.
- The inputs and outputs ordering should be randomized, but BIP69[15] should NOT be used.
The spirit of the proposal as defined here for Versions 00 and 01 is that the new output created by the Proposer for the Receiver should be of the same type as the input being consumed, to somewhat improve privacy, but mostly to ensure that the wallet will be able to understand the new utxo created. However this could get quite complicated in case of customised scriptPubKeys created by more advanced wallets. Hence for simplicity we assume that addresses for which utxos are being consumed MUST be of one of the standard single key types, that is:
- P2PKH (i.e. '1' addresses)
- P2WPKH (segwit native single key, bech32 addresses)
- P2SH-P2WPKH ('3' addresses wrapping segwit native p2wpkh)
The Receiver will need to keep a record of addresses (and corresponding keys) that he has reused. Let's call these addresses A
as above.
The Receiver checks the Bulletin board periodically. He downloads all encrypted blobs that might be relevant (applying filters if the Bulletin board supports this, see section on Bulletin Board below).
For each blob (after being base64 decoded), calling the binary blob o, he should attempt to decrypt according to ECIES-D(p_A, o)
where p_A
is the private key of address A
. As well as performing the checks defined in that operation, he can also check:
- Whether the first 7 bytes match the required SNICKER magic bytes:
0x534e49434b4552
- Whether the version is or is not
0x00
.
c
, and a valid partially signed bitcoin transaction serialization. He then performs the following checks:
- Optional; if not included, the second option in "Storage of Keys" below MUST NOT be assumed to be available (i.e. if not included, the wallet MUST import the new keys and MUST inform the user that persistent wallets are required for funds recovery):
- For the public key of the finalized input in the PSBT, call the pubkey
Q
, calculatec = ECDH(Q, P_A)
and check if it matchesc
. If not, reject.
- For the public key of the finalized input in the PSBT, call the pubkey
- Is one of the destination addresses, the address of the pubkey
P_A + cG
, with the same address type as A. - Ensure that he has safely stored or imported the private key of the new output:
x+c
in the wallet before broadcast of the transaction. - Receiver software must be cognizant of the fact that it is operating on untrusted input. The mere fact of a MAC check passing does not in this case prove anything at all about 'honest' behaviour, since the counterparty is using an entirely ephemeral and untrusted key. In particular it will be vital that the BIP174 parser does not have any vulnerabilities to malicious input.
- After storing the passed
c
-value and on successful parsing of the PSBT into an in-memory transaction proposal, the Receiver software should:- Check that the transaction version is 02, locktime is 0 and input sequence numbers are
0xffffffff
. - Validate the signatures that must exist and be finalized on all but one input. This is to ensure that the Proposer spends their own coins, and not yours.
- Check Receiver's ownership of the unsigned input (remembering that Proposers may have only guessed this and could be wrong). Obviously reject if un-owned.
- Reconstruct the destination - from
P+cG
-P
is already available at this point since we needed it to decrypt the proposal. Apply the same address type to recreate the scriptPubKey, as was mentioned above in "A note on address types". - Check the spent amount against the received amount. The Receiver is free to make their own judgement about the minimum amount of satoshis he receives as (satoshis received minus satoshis spent), it could be less than zero, or more; he should consider the network fee in his calculations of course. This decision can be considered as something set by the free market.
- Assess whether the fee provided to the bitcoin network is suitable. If the transaction is highly desired for some reason, CPFP can be used to bump the fee but a reminder that RBF cannot, because the two sides cannot cooperate to sign a new version.
- Check that the transaction version is 02, locktime is 0 and input sequence numbers are
Assuming all checks pass the Receiver software can co-sign to complete the PSBT, then convert it to Bitcoin network serialization, and broadcast it onto the network, either automatically or based on user input. Implementors are reminded of the point made above, that the private key of the newly created utxo must be stored/imported in advance of broadcast.
New outputs created by SNICKER coinjoins are not directly controlled by a wallet's existing HD tree. Due to the ECDH mechanism used to create the tweaks for these keys, the Receiver wallet has two options:
- Import and persist - The newly created outputs pay to (address of)
P + cG
, which have corresponding private keys(x+c)
, wherex
is the private key ofP
. Most simply, the wallet MAY store these new keys separately (leaving the wallet free to ignore the original tweak valuec
), using an import function, which is already present in many wallets. This does of course leave the user unable to access the funds in the case of recovery from only a BIP32 master secret, so may often be considered insufficient. - Re-derive from blockchain history - Because the tweak values
c
were derived via ECDH between pubkeys of addresses contained within the SNICKER coinjoin, and if the Receiver verified that this derivation was performed correctly at the time of construction of the coinjoin, it will be possible to find all utxos created via this mechanism using (a) the BIP32 master secret and (b) access to historical blockchain data:- Using the master secret and the wallet's HD path, derive addresses as normal
- Use the blockchain to find all transactions that spend from any key in this HD wallet (i.e. all historical transactions, in general). For each spent output owned by us, check if the spending transaction fits the pattern of a SNICKER coinjoin (two equal sized outputs is enough of a check, since false positives are not a problem).
- For each of those transactions, check, for each of the two equal sized outputs, whether one destination address can be regenerated from by taking
c
found in the method described above, and reconstructing a pubkeyP_A + cG
whereP_A
is the Receiver-owned (and previously reused) key, and then constructing the scriptPubKey from that.
The requirements for Proposer and Receiver in Version 01 are the same as those for in Version 00 except where specifically contradicted, or added, in this section.
Here the general concept is: follow the same steps as above, but use the public key from the scriptSig (or witness for segwit) for single key redemptions, thus not requiring address reuse. This introduces a new consideration: the Proposer must guess or infer co-ownership of the input exposing said pubkey, and outputs.
This last point is the reason that, even though Version 01 is much more attractive than Version 00, since it does not require previous address reuse, it is nevertheless a little more complex for both sides to implement, and also the additional inference means the probability of success of any one proposal may be significantly reduced.
The proposer will need to identify a set of transactions for which he has a degree of plausibility (here unspecified) that at least one input is associated with a certain (currently) unspent output. For the sake of plausibility that the proposal will be taken up, he may need to filter according to various other criteria - does the transaction appear to be from a certain wallet, is there some other form of advertisement of "ready to do SNICKER" either embedded in the transaction itself or on some other bulletin board, is the transaction sufficiently recent etc. etc.
It is worthy of note that in the "canonical" case of a two output transaction, the Proposer can try both outputs with any one input; one of the two will almost always be correct.
Assuming he finds a set of such txs T, each one will have a corresponding input I (note: not a utxo, because this already spent) for which he extracts the pubkey P_I
from either the scriptSig or the witness, and there will be a corresponding output which is unspent (see previous paragraphs), whose address we denote as A
.
He will then take these steps (similar but not identical to previous - clarity is preferred here, so nothing is omitted, even if repeated):
- Find one or more utxos of his own to spend, whose total bitcoin value is greater than or equal to: value of utxo owned by
A
, plus approximately the transaction fee for a 2-in 3-out transaction. - Construct a new tweak c (see "Terms" above), using specifically this method (The reason for this specific method was made clear here):
- First, take the public key of the first utxo input (i.e. at the lowest index after randomization) chosen by the Proposer in the previous step, call it
Q
, and its corresponding private keyq
. - Calculate
c = ECDH(Q, P_I)
.
- First, take the public key of the first utxo input (i.e. at the lowest index after randomization) chosen by the Proposer in the previous step, call it
- Construct a new destination address using pubkey
P_I + cG
and of the same address type asA
. - Construct a transaction with inputs: (utxo owned by
A
, his own utxos (the first of which is owned byQ
)) and outputs: (P_I + cG
, with address type as forA
, his own freshly generated output addressesO1
andO2
). The amounts will be such that each party (Proposer, Receiver) receives back approximately what they put into the transaction, but the acceptability of the amount is for the Receiver to decide.O1
MUST receive exactly the same amount asP_I+cG
, and must be of the same scriptPubKey(address) type asA
.O2
should contain the change required to balance for the Proposer. - He signs the transaction for his own inputs, so it is partially signed.
- He serializes the partially signed transaction and the tweak value
c
according to the serialization specification here, calling this serializationM
, he outputs and publishes a Proposal:ECIES-E(P_I, M)
. - He publishes this Proposal (as according to definitions, it should be base64 encoded) to the Bulletin board.
The Receiver actions are as for Version 00, except:
- The Receiver will be keeping track of all used keys/addresses, and not only reused ones, as candidates for proposals.
- (Trivial) the Version byte check uses
0x01
not0x00
. - As explained above in the Proposer actions section for Version 01, the value of
c
is calculated withc = ECDH(Q, P_I)
. However the same comments as for Version 00 apply here in the Receiver's choice of whether to use recoverable keys.
This is not a matter of any protocol-level consensus and so no specification is strictly required outside the format of encrypted proposal, which is already done.
Before continuing it is worthy of note that such a Bulletin Board is not strictly necessary and SNICKER as defined in this BIP can still be carried out without any server, just through ad-hoc connections; as long as the transaction proposal format is standardised, wallets could still create such coinjoins.
Here we will only note some possible, natural implementation approaches, and issues with them:
We earlier mentioned the possibility of using a Tor Hidden Service for this purpose, which seems practical, but in any case network level anonymity for the Proposers who upload and the Receivers who download is more or less essential, as otherwise monitoring (especially by the Bulletin Board) may link the participants in CoinJoins to other metadata, other Bitcoin transactions etc.
Now we'll discuss the possible mechanics of the Bulletin Board's storage of proposals:
The simplest possible approach is for the Bulletin board to accept any and all encrypted blobs and store them in a flat list. This would require Receivers to download ALL proposals and check each of them against each candidate key P. This has a large advantage, namely that the Receiver reveals nothing through their network traffic. But this approach obviously could suffer from both scalability issues (Receiver computation time, bandwidth) and (related) spam attacks since there is no way to restrict fake proposals.
A step up from this (and likely, necessary) is to publish the intended P value in plaintext to the bulletin board, along with the encrypted transaction proposal. This could be considered a privacy leak, however, when we consider that SNICKER of Version 00 or 01 types will be identifiable as such on the blockchain, the existence of a public record of a Proposer choosing such a key is probably not an issue. Also multiple proposals to keys will not be distinguishable due to the encryption, and moreover if the proposal is not taken up, nothing is leaked other than the intent of an unknown party to do a coinjoin with a key that they do not own. With indexing of this type, the Receiver's action is hugely easier, since they will only need to download proposals directly relevant reducing their bandwidth and computation requirements. However, they still need to keep track of all P candidates in advance, of course. Also notably, they could obfuscate their actions somewhat by downloading proposals for a selection of keys, and not just their own.
Even with indexing, the problem of near-infinite fake proposals to clog the bulletin board "channel" still exists. This can be prevented with either (a) hashcash[16] (grind the ephemeral key for ECIES for example), (b)fidelity bonds similar to the idea proposed by Chris Belcher for Joinmarket[17], (c) direct payments to a server for the right to post proposals or (d) proposals only allowed to be made by the Bulletin Board owner or some fixed group. All of these ideas may be possible; but not using any of them would almost certainly lead to a death-by-spam in any reasonably popular public system.
The Versions 00 and 01 as explained above were intended to be (a) as simple as possible and (b) adhering to existing standards wherever possible. Much more variability and customization is possible and could be implemented in higher version numbers and using flags for additional features. Some ideas:
- More efficient variants - if bandwidth is a concern we could reduce the size of proposals, principally by not using the PSBT full format but rather a custom tx and signature transfer format, trading off flexibility for small size. Both SNICKER magic bytes and even tweaks could be removed (tweaks could be inferred from ECDH shared secrets).
- SNICKER as payments - if a payer was willing to wait a little longer than normal, a new variant of SNICKER may have a particularly good incentive alignment - the payer-Proposer could incentivise the Receiver to enable the payment of a fixed amount of the Proposer's choosing to a destination by adding a little to the Receiver's outputs. This would not fit in Versions 00 and 01 as current since it would require two outputs for the Receiver.
- Use other features of Bitcoin like OP_RETURN or sign-to-contract or possibly "stealth addresses" to allow flagging of individual transactions or utxos as SNICKER candidates (improving discovery) (it is worth noting that pretty much any 'watermark' in existing transactions, like CoinJoins of various types, as well as address reuse, could be used today as such a discovery feature).
- Allow SNICKER on more complex/custom scriptPubKey types than those listed above.
- Allow more complex transaction structures.
- More than 2 party joins based on multiple proposal transfers - a significantly more complex protocol.
SNICKER has no backwards compatibility concerns with respect to Bitcoin and is a purely optional client-side wallet feature.
TODO
TODO
TODO
Thanks in particular to @fivepiece (github) aka arubi (IRC freenode) who came up with many ideas here including specifically Version 01, and many others who offered thoughts on this concept.
- ^ https://joinmarket.me/blog/blog/snicker/
- ^ https://bitcointalk.org/index.php?topic=279249.0
- ^ https://en.bitcoin.it/wiki/Shared_coin - warning, link outdated, this system is defunct
- ^ https://github.com/darkwallet/darkwallet - as for previous, this is now defunct
- ^ https://github.com/Joinmarket-Org/joinmarket-clientserver
- ^ https://github.com/zkSNACKs/WalletWasabi
- ^ https://github.com/Samourai-Wallet
- ^ https://bitcointalk.org/index.php?topic=1497271 for announcement and https://www.ndss-symposium.org/ndss2017/ndss-2017-programme/p2p-mixing-and-unlinkable-bitcoin-transactions/ for latest version
- ^ https://github.com/sipa/bips/blob/bip-schnorr/bip-taproot.mediawiki#Constructing_and_spending_Taproot_outputs
- ^ ECDH implementation in libsecp256k1: https://github.com/bitcoin/bitcoin/blob/master/src/secp256k1/src/modules/ecdh/main_impl.h
- ^ https://en.wikipedia.org/wiki/Integrated_Encryption_Scheme
- ^ https://github.com/spesmilo/electrum/blob/fd5b1acdc896fbe86d585b2e721edde7a357afc3/electrum/ecc.py#L277-L292
- ^ https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
- ^ https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki
- ^ https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki
- ^ http://hashcash.org/
- ^ https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2019-July/017169.html
Thanks for the link! :)