Problem
Coinjoin process requires lot of interaction between peers
Research
SIGHASH_ALL | SIGHASH_ANYONECANPAY
reduces some steps- Nostr encrypted channels can be used so that users don’t need to be always online
- Payment Pools allow rolling coinjoin but not possible without covenants
Solution
Using SIGHASH_ALL | SIGHASH_ANYONECANPAY
and nostr encrypted channels we can create pools in which users can enter any time and exit together. Unilateral exits are also possible which requires lot of pre-signed transactions, multisig and some interaction.
I have used this approach in electrum plugin for joinstr which is still a work in progress. There are different ways to create nostr channels and I have used basic method using NIP 4. See other NIPs in nostr repository that can be used to implement better encrypted channels.
Paid relay is used to avoid denial of service attacks and below are 2 functions used for creating and joining pools:
def create_channel(self, denomination, peers, timeout, address, wallet, tab_widget):
letters = string.ascii_lowercase
pool_id = ''.join(random.choice(letters) for i in range(10)) + str(int(time.time()))
private_key = PrivateKey()
pub_key=private_key.public_key.hex()
channel = {"type": "new_pool","id": pool_id, "public_key": pub_key,"denomination": float(denomination), "peers": int(peers), "timeout": int(time.time())+timeout, "relay": self.config.get('nostr_relay', 0)}
channel_creds = channel
channel_creds["private_key"] = private_key.hex()
pool_path = os.path.expanduser(os.path.join('~', '.electrum', "pools.json"))
if os.path.exists(pool_path) and os.stat(pool_path).st_size > 0:
with open(pool_path, 'r') as file:
pools = json.load(file)
else:
pools = []
pools.append(channel_creds)
with open(pool_path, 'w') as file:
json.dump(pools, file, indent=2)
self.publish_event(channel, 999999)
self.register_output(wallet,address, tab_widget)
thread = threading.Thread(target=self.run_checkevents, args=('pool_msg','join_request', None, None, None, None, pub_key, peers))
thread.daemon = True
thread.start()
def join_channel(self, request, pubkey, relay):
event = self.publish_event(request, 4, pubkey, relay=relay, qr=True)
if not hasattr(self, 'dialog'):
self.dialog = QDialog()
self.dialog.setWindowTitle("Pool Request")
self.dialog_layout = QVBoxLayout()
self.dialog.setLayout(self.dialog_layout)
self.message_label = QLabel("Waiting for pool credentials...")
self.message_label.setAlignment(Qt.AlignCenter)
self.dialog_layout.addWidget(self.message_label)
self.progress_bar = QProgressBar()
self.progress_bar.setTextVisible(False)
self.dialog_layout.addWidget(self.progress_bar)
self.dialog.setFixedWidth(400)
self.dialog.show()
self.timer = QTimer()
self.timer.timeout.connect(self.update_progress)
self.timer.start(100)
thread = threading.Thread(target=self.run_checkevents, args=('own_msg', None, None, None, None, event.public_key))
thread.daemon = True
thread.start()
There was some discussion about the terms so I have just called them “pools”. Such pools are not limited to coinjoin and could be used for other things. Example: Discreet Log Contracts