23 changed files with 3526 additions and 94 deletions
@ -0,0 +1,964 @@
|
||||
# -*- coding: utf-8 -*- |
||||
|
||||
from pprint import pprint |
||||
|
||||
from hashlib import sha256 |
||||
from unittest import IsolatedAsyncioTestCase |
||||
|
||||
import jmclient # install asyncioreactor |
||||
from twisted.internet import reactor |
||||
|
||||
import pytest |
||||
|
||||
import jmbitcoin as btc |
||||
from jmbase import get_log |
||||
from jmclient import ( |
||||
load_test_config, jm_single, get_network, cryptoengine, VolatileStorage, |
||||
FrostWallet, WalletService) |
||||
from jmclient.frost_clients import DKGClient, FROSTClient |
||||
from jmfrost.chilldkg_ref.chilldkg import ( |
||||
hostpubkey_gen, ParticipantMsg1, CoordinatorMsg1, ParticipantMsg2, |
||||
CoordinatorMsg2) |
||||
|
||||
|
||||
pytestmark = pytest.mark.usefixtures("setup_regtest_frost_bitcoind") |
||||
|
||||
log = get_log() |
||||
|
||||
|
||||
async def get_populated_wallet(entropy=None): |
||||
storage = VolatileStorage() |
||||
dkg_storage = VolatileStorage() |
||||
recovery_storage = VolatileStorage() |
||||
FrostWallet.initialize(storage, dkg_storage, recovery_storage, |
||||
get_network(), entropy=entropy) |
||||
wlt = FrostWallet(storage, dkg_storage, recovery_storage) |
||||
await wlt.async_init(storage) |
||||
return wlt |
||||
|
||||
|
||||
async def populate_dkg_session(test_case): |
||||
dkgc1 = DKGClient(test_case.wlt_svc1) |
||||
dkgc2 = DKGClient(test_case.wlt_svc2) |
||||
dkgc3 = DKGClient(test_case.wlt_svc3) |
||||
hostpubkeyhash_hex, session_id, sig_hex = dkgc1.dkg_init(0, 0, 0) |
||||
|
||||
( |
||||
nick1, |
||||
hostpubkeyhash2_hex, |
||||
session_id2_hex, |
||||
sig2_hex, |
||||
pmsg1_2 |
||||
) = dkgc2.on_dkg_init( |
||||
test_case.nick1, hostpubkeyhash_hex, session_id, sig_hex) |
||||
pmsg1_2 = dkgc2.deserialize_pmsg1(pmsg1_2) |
||||
|
||||
( |
||||
nick1, |
||||
hostpubkeyhash3_hex, |
||||
session_id3_hex, |
||||
sig3_hex, |
||||
pmsg1_3 |
||||
) = dkgc3.on_dkg_init( |
||||
test_case.nick1, hostpubkeyhash_hex, session_id, sig_hex) |
||||
pmsg1_3 = dkgc2.deserialize_pmsg1(pmsg1_3) |
||||
|
||||
ready_list, cmsg1 = dkgc1.on_dkg_pmsg1( |
||||
test_case.nick2, hostpubkeyhash2_hex, session_id, sig2_hex, pmsg1_2) |
||||
ready_list, cmsg1 = dkgc1.on_dkg_pmsg1( |
||||
test_case.nick3, hostpubkeyhash3_hex, session_id, sig3_hex, pmsg1_3) |
||||
cmsg1 = dkgc1.deserialize_cmsg1(cmsg1) |
||||
|
||||
pmsg2_2 = dkgc2.party_step2(session_id, cmsg1) |
||||
pmsg2_2 = dkgc2.deserialize_pmsg2(pmsg2_2) |
||||
pmsg2_3 = dkgc3.party_step2(session_id, cmsg1) |
||||
pmsg2_3 = dkgc3.deserialize_pmsg2(pmsg2_3) |
||||
|
||||
ready_list, cmsg2, ext_recovery = dkgc1.on_dkg_pmsg2( |
||||
test_case.nick2, session_id, pmsg2_2) |
||||
ready_list, cmsg2, ext_recovery = dkgc1.on_dkg_pmsg2( |
||||
test_case.nick3, session_id, pmsg2_3) |
||||
cmsg2 = dkgc3.deserialize_cmsg2(cmsg2) |
||||
|
||||
assert dkgc2.finalize(session_id, cmsg2, ext_recovery) |
||||
assert dkgc3.finalize(session_id, cmsg2, ext_recovery) |
||||
dkgc1.on_dkg_finalized(test_case.nick2, session_id) |
||||
dkgc1.on_dkg_finalized(test_case.nick3, session_id) |
||||
return session_id |
||||
|
||||
|
||||
class DKGClientTestCaseBase(IsolatedAsyncioTestCase): |
||||
|
||||
def setUp(self): |
||||
load_test_config(config_path='./test_frost') |
||||
btc.select_chain_params("bitcoin/regtest") |
||||
cryptoengine.BTC_P2TR.VBYTE = 100 |
||||
jm_single().bc_interface.tick_forward_chain_interval = 2 |
||||
|
||||
async def asyncSetUp(self): |
||||
entropy1 = bytes.fromhex('8e5e5677fb302874a607b63ad03ba434') |
||||
entropy2 = bytes.fromhex('38dfa80fbb21b32b2b2740e00a47de9d') |
||||
entropy3 = bytes.fromhex('3ad9c77fcd1d537b6ef396952d1221a0') |
||||
# entropy4 wor wallet with hospubkey not in joinmarket.cfg |
||||
entropy4 = bytes.fromhex('ce88b87f6c85d651e416b8173ab95e57') |
||||
self.wlt1 = await get_populated_wallet(entropy1) |
||||
self.hostpubkey1 = hostpubkey_gen(self.wlt1._hostseckey[:32]) |
||||
self.wlt_svc1 = WalletService(self.wlt1) |
||||
self.wlt2 = await get_populated_wallet(entropy2) |
||||
self.hostpubkey2 = hostpubkey_gen(self.wlt2._hostseckey[:32]) |
||||
self.wlt_svc2 = WalletService(self.wlt2) |
||||
self.wlt3 = await get_populated_wallet(entropy3) |
||||
self.hostpubkey3 = hostpubkey_gen(self.wlt3._hostseckey[:32]) |
||||
self.wlt_svc3 = WalletService(self.wlt3) |
||||
self.wlt4= await get_populated_wallet(entropy4) |
||||
self.hostpubkey4 = hostpubkey_gen(self.wlt4._hostseckey[:32]) |
||||
self.wlt_svc4 = WalletService(self.wlt4) |
||||
self.nick1, self.nick2, self.nick3, self.nick4 = [ |
||||
'nick1', 'nick2', 'nick3', 'nick4' |
||||
] |
||||
|
||||
|
||||
class DKGClientTestCase(DKGClientTestCaseBase): |
||||
|
||||
async def test_dkg_init(self): |
||||
# test wallet with unknown hostpubkey |
||||
dkgc1 = DKGClient(self.wlt_svc4) |
||||
hostpubkeyhash_hex, session_id, sig_hex = dkgc1.dkg_init(0, 0, 0) |
||||
assert hostpubkeyhash_hex is None |
||||
assert session_id is None |
||||
assert sig_hex is None |
||||
|
||||
dkgc1 = DKGClient(self.wlt_svc1) |
||||
hostpubkeyhash_hex, session_id, sig_hex = dkgc1.dkg_init(0, 0, 0) |
||||
assert hostpubkeyhash_hex and len(hostpubkeyhash_hex) == 64 |
||||
assert session_id and len(session_id) == 32 |
||||
assert sig_hex and len(sig_hex) == 128 |
||||
|
||||
async def test_on_dkg_init(self): |
||||
dkgc1 = DKGClient(self.wlt_svc1) |
||||
dkgc2 = DKGClient(self.wlt_svc2) |
||||
hostpubkeyhash_hex, session_id, sig_hex = dkgc1.dkg_init(0, 0, 0) |
||||
|
||||
# fail with wrong pubkeyhash |
||||
hostpubkeyhash4_hex = sha256(self.hostpubkey4).digest() |
||||
( |
||||
nick1, |
||||
hostpubkeyhash2_hex, |
||||
session_id2_hex, |
||||
sig2_hex, |
||||
pmsg1 |
||||
) = dkgc2.on_dkg_init( |
||||
self.nick1, hostpubkeyhash4_hex, session_id, sig_hex) |
||||
for v in [nick1, hostpubkeyhash2_hex, session_id2_hex, |
||||
sig2_hex, pmsg1]: |
||||
assert v is None |
||||
|
||||
# fail with wrong sig |
||||
( |
||||
nick1, |
||||
hostpubkeyhash2_hex, |
||||
session_id2_hex, |
||||
sig2_hex, |
||||
pmsg1 |
||||
) = dkgc2.on_dkg_init( |
||||
self.nick1, hostpubkeyhash_hex, session_id, '01020304'*16) |
||||
for v in [nick1, hostpubkeyhash2_hex, session_id2_hex, |
||||
sig2_hex, pmsg1]: |
||||
assert v is None |
||||
|
||||
( |
||||
nick1, |
||||
hostpubkeyhash2_hex, |
||||
session_id2_hex, |
||||
sig2_hex, |
||||
pmsg1 |
||||
) = dkgc2.on_dkg_init( |
||||
self.nick1, hostpubkeyhash_hex, session_id, sig_hex) |
||||
assert nick1 == self.nick1 |
||||
assert hostpubkeyhash2_hex and len(hostpubkeyhash2_hex) == 64 |
||||
assert session_id2_hex and len(session_id2_hex) == 64 |
||||
assert bytes.fromhex(session_id2_hex) == session_id |
||||
assert sig_hex and len(sig_hex) == 128 |
||||
assert pmsg1 is not None |
||||
|
||||
# fail on second call with right params |
||||
( |
||||
nick1, |
||||
hostpubkeyhash2_hex, |
||||
session_id2_hex, |
||||
sig2_hex, |
||||
pmsg1 |
||||
) = dkgc2.on_dkg_init( |
||||
self.nick1, hostpubkeyhash_hex, session_id, sig_hex) |
||||
for v in [nick1, hostpubkeyhash2_hex, session_id2_hex, |
||||
sig2_hex, pmsg1]: |
||||
assert v is None |
||||
|
||||
async def test_party_step1(self): |
||||
dkgc1 = DKGClient(self.wlt_svc1) |
||||
dkgc2 = DKGClient(self.wlt_svc2) |
||||
hostpubkeyhash_hex, session_id, sig_hex = dkgc1.dkg_init(0, 0, 0) |
||||
|
||||
( |
||||
nick1, |
||||
hostpubkeyhash2_hex, |
||||
session_id2_hex, |
||||
sig2_hex, |
||||
pmsg1 |
||||
) = dkgc2.on_dkg_init( |
||||
self.nick1, hostpubkeyhash_hex, session_id, sig_hex) |
||||
|
||||
# fail with unknown session_id |
||||
pmsg1 = dkgc2.party_step1(b'\x05'*32) |
||||
assert pmsg1 is None |
||||
|
||||
# fail when session.state1 aleready set |
||||
pmsg1 = dkgc2.party_step1(session_id) |
||||
assert pmsg1 is None |
||||
|
||||
session = dkgc2.dkg_sessions.get(session_id) |
||||
session.state1 = None |
||||
pmsg1 = dkgc2.party_step1(session_id) |
||||
assert pmsg1 is not None |
||||
assert isinstance(pmsg1, bytes) |
||||
|
||||
session.state1 = None |
||||
pmsg1 = dkgc2.party_step1(session_id, serialize=False) |
||||
assert pmsg1 is not None |
||||
assert isinstance(pmsg1, ParticipantMsg1) |
||||
|
||||
def test_on_dkg_pmsg1(self): |
||||
dkgc1 = DKGClient(self.wlt_svc1) |
||||
dkgc2 = DKGClient(self.wlt_svc2) |
||||
dkgc3 = DKGClient(self.wlt_svc3) |
||||
hostpubkeyhash_hex, session_id, sig_hex = dkgc1.dkg_init(0, 0, 0) |
||||
|
||||
( |
||||
nick1, |
||||
hostpubkeyhash2_hex, |
||||
session_id2_hex, |
||||
sig2_hex, |
||||
pmsg1_2 |
||||
) = dkgc2.on_dkg_init( |
||||
self.nick1, hostpubkeyhash_hex, session_id, sig_hex) |
||||
pmsg1_2 = dkgc2.deserialize_pmsg1(pmsg1_2) |
||||
|
||||
( |
||||
nick1, |
||||
hostpubkeyhash3_hex, |
||||
session_id3_hex, |
||||
sig3_hex, |
||||
pmsg1_3 |
||||
) = dkgc3.on_dkg_init( |
||||
self.nick1, hostpubkeyhash_hex, session_id, sig_hex) |
||||
pmsg1_3 = dkgc2.deserialize_pmsg1(pmsg1_3) |
||||
|
||||
# party2 added pmsg1, no ready_list, no cmsg1 returned yet |
||||
ready_list, cmsg1 = dkgc1.on_dkg_pmsg1( |
||||
self.nick2, hostpubkeyhash2_hex, session_id, sig2_hex, pmsg1_2) |
||||
assert ready_list is None |
||||
assert cmsg1 is None |
||||
|
||||
# unknown coordinator session |
||||
ready_list, cmsg1 = dkgc1.on_dkg_pmsg1( |
||||
self.nick3, hostpubkeyhash3_hex, b'\xaa'*32, sig3_hex, pmsg1_3) |
||||
assert ready_list is None |
||||
assert cmsg1 is None |
||||
|
||||
# unknown pubkeyhash |
||||
ready_list, cmsg1 = dkgc1.on_dkg_pmsg1( |
||||
self.nick3, b'\xaa'*32, session_id, sig3_hex, pmsg1_3) |
||||
assert ready_list is None |
||||
assert cmsg1 is None |
||||
|
||||
# wrong sig |
||||
ready_list, cmsg1 = dkgc1.on_dkg_pmsg1( |
||||
self.nick3, hostpubkeyhash3_hex, session_id, 'aa'*64, pmsg1_3) |
||||
assert ready_list is None |
||||
assert cmsg1 is None |
||||
|
||||
ready_list, cmsg1 = dkgc1.on_dkg_pmsg1( |
||||
self.nick3, hostpubkeyhash3_hex, session_id, sig3_hex, pmsg1_3) |
||||
assert ready_list == set([self.nick2, self.nick3]) |
||||
cmsg1 = dkgc1.deserialize_cmsg1(cmsg1) |
||||
assert isinstance(cmsg1, CoordinatorMsg1) |
||||
|
||||
def test_coordinator_step1(self): |
||||
dkgc1 = DKGClient(self.wlt_svc1) |
||||
dkgc2 = DKGClient(self.wlt_svc2) |
||||
dkgc3 = DKGClient(self.wlt_svc3) |
||||
hostpubkeyhash_hex, session_id, sig_hex = dkgc1.dkg_init(0, 0, 0) |
||||
|
||||
( |
||||
nick1, |
||||
hostpubkeyhash2_hex, |
||||
session_id2_hex, |
||||
sig2_hex, |
||||
pmsg1_2 |
||||
) = dkgc2.on_dkg_init( |
||||
self.nick1, hostpubkeyhash_hex, session_id, sig_hex) |
||||
pmsg1_2 = dkgc2.deserialize_pmsg1(pmsg1_2) |
||||
|
||||
( |
||||
nick1, |
||||
hostpubkeyhash3_hex, |
||||
session_id3_hex, |
||||
sig3_hex, |
||||
pmsg1_3 |
||||
) = dkgc3.on_dkg_init( |
||||
self.nick1, hostpubkeyhash_hex, session_id, sig_hex) |
||||
pmsg1_3 = dkgc2.deserialize_pmsg1(pmsg1_3) |
||||
|
||||
ready_list, cmsg1 = dkgc1.on_dkg_pmsg1( |
||||
self.nick2, hostpubkeyhash2_hex, session_id, sig2_hex, pmsg1_2) |
||||
ready_list, cmsg1 = dkgc1.on_dkg_pmsg1( |
||||
self.nick3, hostpubkeyhash3_hex, session_id, sig3_hex, pmsg1_3) |
||||
|
||||
# unknown session_id |
||||
cmsg1 = dkgc1.coordinator_step1(b'\xaa'*32) |
||||
assert cmsg1 is None |
||||
|
||||
# coordinator.state already set |
||||
cmsg1 = dkgc1.coordinator_step1(session_id) |
||||
assert cmsg1 is None |
||||
|
||||
coordinator = dkgc1.dkg_coordinators.get(session_id) |
||||
coordinator.state = None |
||||
cmsg1 = dkgc1.coordinator_step1(session_id) |
||||
|
||||
def test_party_step2(self): |
||||
dkgc1 = DKGClient(self.wlt_svc1) |
||||
dkgc2 = DKGClient(self.wlt_svc2) |
||||
dkgc3 = DKGClient(self.wlt_svc3) |
||||
hostpubkeyhash_hex, session_id, sig_hex = dkgc1.dkg_init(0, 0, 0) |
||||
|
||||
( |
||||
nick1, |
||||
hostpubkeyhash2_hex, |
||||
session_id2_hex, |
||||
sig2_hex, |
||||
pmsg1_2 |
||||
) = dkgc2.on_dkg_init( |
||||
self.nick1, hostpubkeyhash_hex, session_id, sig_hex) |
||||
pmsg1_2 = dkgc2.deserialize_pmsg1(pmsg1_2) |
||||
|
||||
( |
||||
nick1, |
||||
hostpubkeyhash3_hex, |
||||
session_id3_hex, |
||||
sig3_hex, |
||||
pmsg1_3 |
||||
) = dkgc3.on_dkg_init( |
||||
self.nick1, hostpubkeyhash_hex, session_id, sig_hex) |
||||
pmsg1_3 = dkgc2.deserialize_pmsg1(pmsg1_3) |
||||
|
||||
ready_list, cmsg1 = dkgc1.on_dkg_pmsg1( |
||||
self.nick2, hostpubkeyhash2_hex, session_id, sig2_hex, pmsg1_2) |
||||
ready_list, cmsg1 = dkgc1.on_dkg_pmsg1( |
||||
self.nick3, hostpubkeyhash3_hex, session_id, sig3_hex, pmsg1_3) |
||||
cmsg1 = dkgc1.deserialize_cmsg1(cmsg1) |
||||
|
||||
# unknown session_id |
||||
pmsg2 = dkgc2.party_step2(b'\xaa'*32, cmsg1) |
||||
assert pmsg2 is None |
||||
|
||||
pmsg2 = dkgc2.party_step2(session_id, cmsg1) |
||||
assert cmsg1 is not None |
||||
pmsg2 = dkgc1.deserialize_pmsg2(pmsg2) |
||||
assert isinstance(pmsg2, ParticipantMsg2) |
||||
|
||||
# session.state2 already set |
||||
pmsg2 = dkgc2.party_step2(session_id, cmsg1) |
||||
assert pmsg2 is None |
||||
|
||||
def test_on_dkg_pmsg2(self): |
||||
dkgc1 = DKGClient(self.wlt_svc1) |
||||
dkgc2 = DKGClient(self.wlt_svc2) |
||||
dkgc3 = DKGClient(self.wlt_svc3) |
||||
hostpubkeyhash_hex, session_id, sig_hex = dkgc1.dkg_init(0, 0, 0) |
||||
|
||||
( |
||||
nick1, |
||||
hostpubkeyhash2_hex, |
||||
session_id2_hex, |
||||
sig2_hex, |
||||
pmsg1_2 |
||||
) = dkgc2.on_dkg_init( |
||||
self.nick1, hostpubkeyhash_hex, session_id, sig_hex) |
||||
pmsg1_2 = dkgc2.deserialize_pmsg1(pmsg1_2) |
||||
|
||||
( |
||||
nick1, |
||||
hostpubkeyhash3_hex, |
||||
session_id3_hex, |
||||
sig3_hex, |
||||
pmsg1_3 |
||||
) = dkgc3.on_dkg_init( |
||||
self.nick1, hostpubkeyhash_hex, session_id, sig_hex) |
||||
pmsg1_3 = dkgc2.deserialize_pmsg1(pmsg1_3) |
||||
|
||||
ready_list, cmsg1 = dkgc1.on_dkg_pmsg1( |
||||
self.nick2, hostpubkeyhash2_hex, session_id, sig2_hex, pmsg1_2) |
||||
ready_list, cmsg1 = dkgc1.on_dkg_pmsg1( |
||||
self.nick3, hostpubkeyhash3_hex, session_id, sig3_hex, pmsg1_3) |
||||
cmsg1 = dkgc1.deserialize_cmsg1(cmsg1) |
||||
|
||||
pmsg2_2 = dkgc2.party_step2(session_id, cmsg1) |
||||
pmsg2_2 = dkgc2.deserialize_pmsg2(pmsg2_2) |
||||
assert isinstance(pmsg2_2, ParticipantMsg2) |
||||
pmsg2_3 = dkgc3.party_step2(session_id, cmsg1) |
||||
pmsg2_3 = dkgc3.deserialize_pmsg2(pmsg2_3) |
||||
assert isinstance(pmsg2_3, ParticipantMsg2) |
||||
|
||||
# party2 added pmsg2, no ready_list, no cmsg2 returned yet |
||||
ready_list, cmsg2, ext_recovery = dkgc1.on_dkg_pmsg2( |
||||
self.nick2, session_id, pmsg2_2) |
||||
assert ready_list is None |
||||
assert cmsg2 is None |
||||
assert ext_recovery is None |
||||
|
||||
# unknown coordinator session |
||||
ready_list, cmsg2, ext_recovery = dkgc1.on_dkg_pmsg2( |
||||
self.nick3, b'\xaa'*32, pmsg2_3) |
||||
assert ready_list is None |
||||
assert cmsg2 is None |
||||
assert ext_recovery is None |
||||
|
||||
# unknown party nick |
||||
ready_list, cmsg2, ext_recovery = dkgc1.on_dkg_pmsg2( |
||||
self.nick4, session_id, pmsg2_3) |
||||
assert ready_list is None |
||||
assert cmsg2 is None |
||||
assert ext_recovery is None |
||||
|
||||
ready_list, cmsg2, ext_recovery = dkgc1.on_dkg_pmsg2( |
||||
self.nick3, session_id, pmsg2_3) |
||||
cmsg2 = dkgc1.deserialize_cmsg2(cmsg2) |
||||
assert ready_list == set([self.nick2, self.nick3]) |
||||
assert isinstance(cmsg2, CoordinatorMsg2) |
||||
assert isinstance(ext_recovery, bytes) |
||||
|
||||
# party pubkey for nick3 not found |
||||
coordinator = dkgc1.dkg_coordinators.get(session_id) |
||||
session3 = coordinator.sessions.pop(self.hostpubkey3) |
||||
ready_list, cmsg2, ext_recovery = dkgc1.on_dkg_pmsg2( |
||||
self.nick3, session_id, pmsg2_3) |
||||
assert ready_list is None |
||||
assert cmsg2 is None |
||||
assert ext_recovery is None |
||||
coordinator.sessions[self.hostpubkey3] = session3 |
||||
|
||||
# pmsg2 already set in coordinator sessions |
||||
ready_list, cmsg2, ext_recovery = dkgc1.on_dkg_pmsg2( |
||||
self.nick3, session_id, pmsg2_3) |
||||
assert ready_list is None |
||||
assert cmsg2 is None |
||||
assert ext_recovery is None |
||||
|
||||
def test_coordinator_step2(self): |
||||
dkgc1 = DKGClient(self.wlt_svc1) |
||||
dkgc2 = DKGClient(self.wlt_svc2) |
||||
dkgc3 = DKGClient(self.wlt_svc3) |
||||
hostpubkeyhash_hex, session_id, sig_hex = dkgc1.dkg_init(0, 0, 0) |
||||
|
||||
( |
||||
nick1, |
||||
hostpubkeyhash2_hex, |
||||
session_id2_hex, |
||||
sig2_hex, |
||||
pmsg1_2 |
||||
) = dkgc2.on_dkg_init( |
||||
self.nick1, hostpubkeyhash_hex, session_id, sig_hex) |
||||
pmsg1_2 = dkgc2.deserialize_pmsg1(pmsg1_2) |
||||
|
||||
( |
||||
nick1, |
||||
hostpubkeyhash3_hex, |
||||
session_id3_hex, |
||||
sig3_hex, |
||||
pmsg1_3 |
||||
) = dkgc3.on_dkg_init( |
||||
self.nick1, hostpubkeyhash_hex, session_id, sig_hex) |
||||
pmsg1_3 = dkgc2.deserialize_pmsg1(pmsg1_3) |
||||
|
||||
ready_list, cmsg1 = dkgc1.on_dkg_pmsg1( |
||||
self.nick2, hostpubkeyhash2_hex, session_id, sig2_hex, pmsg1_2) |
||||
ready_list, cmsg1 = dkgc1.on_dkg_pmsg1( |
||||
self.nick3, hostpubkeyhash3_hex, session_id, sig3_hex, pmsg1_3) |
||||
cmsg1 = dkgc1.deserialize_cmsg1(cmsg1) |
||||
|
||||
pmsg2_2 = dkgc2.party_step2(session_id, cmsg1) |
||||
pmsg2_2 = dkgc2.deserialize_pmsg2(pmsg2_2) |
||||
pmsg2_3 = dkgc3.party_step2(session_id, cmsg1) |
||||
pmsg2_3 = dkgc3.deserialize_pmsg2(pmsg2_3) |
||||
|
||||
ready_list, cmsg2, ext_recovery = dkgc1.on_dkg_pmsg2( |
||||
self.nick2, session_id, pmsg2_2) |
||||
ready_list, cmsg2, ext_recovery = dkgc1.on_dkg_pmsg2( |
||||
self.nick3, session_id, pmsg2_3) |
||||
|
||||
# unknown session_id |
||||
cmsg2 = dkgc1.coordinator_step2(b'\xaa'*32) |
||||
assert cmsg2 is None |
||||
|
||||
# coordinator.cmsg2 already set |
||||
cmsg2 = dkgc1.coordinator_step2(session_id) |
||||
assert cmsg2 is None |
||||
|
||||
coordinator = dkgc1.dkg_coordinators.get(session_id) |
||||
coordinator.cmsg2 = None |
||||
cmsg2 = dkgc1.coordinator_step2(session_id) |
||||
cmsg2 = dkgc1.deserialize_cmsg2(cmsg2) |
||||
assert isinstance(cmsg2, CoordinatorMsg2) |
||||
|
||||
def test_dkg_finalize(self): |
||||
dkgc1 = DKGClient(self.wlt_svc1) |
||||
dkgc2 = DKGClient(self.wlt_svc2) |
||||
dkgc3 = DKGClient(self.wlt_svc3) |
||||
hostpubkeyhash_hex, session_id, sig_hex = dkgc1.dkg_init(0, 0, 0) |
||||
|
||||
( |
||||
nick1, |
||||
hostpubkeyhash2_hex, |
||||
session_id2_hex, |
||||
sig2_hex, |
||||
pmsg1_2 |
||||
) = dkgc2.on_dkg_init( |
||||
self.nick1, hostpubkeyhash_hex, session_id, sig_hex) |
||||
pmsg1_2 = dkgc2.deserialize_pmsg1(pmsg1_2) |
||||
|
||||
( |
||||
nick1, |
||||
hostpubkeyhash3_hex, |
||||
session_id3_hex, |
||||
sig3_hex, |
||||
pmsg1_3 |
||||
) = dkgc3.on_dkg_init( |
||||
self.nick1, hostpubkeyhash_hex, session_id, sig_hex) |
||||
pmsg1_3 = dkgc2.deserialize_pmsg1(pmsg1_3) |
||||
|
||||
ready_list, cmsg1 = dkgc1.on_dkg_pmsg1( |
||||
self.nick2, hostpubkeyhash2_hex, session_id, sig2_hex, pmsg1_2) |
||||
ready_list, cmsg1 = dkgc1.on_dkg_pmsg1( |
||||
self.nick3, hostpubkeyhash3_hex, session_id, sig3_hex, pmsg1_3) |
||||
cmsg1 = dkgc1.deserialize_cmsg1(cmsg1) |
||||
|
||||
pmsg2_2 = dkgc2.party_step2(session_id, cmsg1) |
||||
pmsg2_2 = dkgc2.deserialize_pmsg2(pmsg2_2) |
||||
pmsg2_3 = dkgc3.party_step2(session_id, cmsg1) |
||||
pmsg2_3 = dkgc3.deserialize_pmsg2(pmsg2_3) |
||||
|
||||
ready_list, cmsg2, ext_recovery = dkgc1.on_dkg_pmsg2( |
||||
self.nick2, session_id, pmsg2_2) |
||||
ready_list, cmsg2, ext_recovery = dkgc1.on_dkg_pmsg2( |
||||
self.nick3, session_id, pmsg2_3) |
||||
cmsg2 = dkgc3.deserialize_cmsg2(cmsg2) |
||||
|
||||
# unknown session_id |
||||
assert not dkgc2.finalize(b'\xaa'*32, cmsg2, ext_recovery) |
||||
|
||||
assert dkgc2.finalize(session_id, cmsg2, ext_recovery) |
||||
assert dkgc3.finalize(session_id, cmsg2, ext_recovery) |
||||
|
||||
# session.dkg_output already set |
||||
assert not dkgc2.finalize(session_id, cmsg2, ext_recovery) |
||||
assert not dkgc3.finalize(session_id, cmsg2, ext_recovery) |
||||
|
||||
def test_on_dkg_finalized(self): |
||||
dkgc1 = DKGClient(self.wlt_svc1) |
||||
dkgc2 = DKGClient(self.wlt_svc2) |
||||
dkgc3 = DKGClient(self.wlt_svc3) |
||||
hostpubkeyhash_hex, session_id, sig_hex = dkgc1.dkg_init(0, 0, 0) |
||||
|
||||
( |
||||
nick1, |
||||
hostpubkeyhash2_hex, |
||||
session_id2_hex, |
||||
sig2_hex, |
||||
pmsg1_2 |
||||
) = dkgc2.on_dkg_init( |
||||
self.nick1, hostpubkeyhash_hex, session_id, sig_hex) |
||||
pmsg1_2 = dkgc2.deserialize_pmsg1(pmsg1_2) |
||||
|
||||
( |
||||
nick1, |
||||
hostpubkeyhash3_hex, |
||||
session_id3_hex, |
||||
sig3_hex, |
||||
pmsg1_3 |
||||
) = dkgc3.on_dkg_init( |
||||
self.nick1, hostpubkeyhash_hex, session_id, sig_hex) |
||||
pmsg1_3 = dkgc2.deserialize_pmsg1(pmsg1_3) |
||||
|
||||
ready_list, cmsg1 = dkgc1.on_dkg_pmsg1( |
||||
self.nick2, hostpubkeyhash2_hex, session_id, sig2_hex, pmsg1_2) |
||||
ready_list, cmsg1 = dkgc1.on_dkg_pmsg1( |
||||
self.nick3, hostpubkeyhash3_hex, session_id, sig3_hex, pmsg1_3) |
||||
cmsg1 = dkgc1.deserialize_cmsg1(cmsg1) |
||||
|
||||
pmsg2_2 = dkgc2.party_step2(session_id, cmsg1) |
||||
pmsg2_2 = dkgc2.deserialize_pmsg2(pmsg2_2) |
||||
pmsg2_3 = dkgc3.party_step2(session_id, cmsg1) |
||||
pmsg2_3 = dkgc3.deserialize_pmsg2(pmsg2_3) |
||||
|
||||
ready_list, cmsg2, ext_recovery = dkgc1.on_dkg_pmsg2( |
||||
self.nick2, session_id, pmsg2_2) |
||||
ready_list, cmsg2, ext_recovery = dkgc1.on_dkg_pmsg2( |
||||
self.nick3, session_id, pmsg2_3) |
||||
cmsg2 = dkgc3.deserialize_cmsg2(cmsg2) |
||||
|
||||
assert dkgc2.finalize(session_id, cmsg2, ext_recovery) |
||||
assert dkgc3.finalize(session_id, cmsg2, ext_recovery) |
||||
|
||||
# unknown session_id |
||||
dkgc1.on_dkg_finalized(self.nick2, b'\xaa'*32) |
||||
|
||||
assert not dkgc1.on_dkg_finalized(self.nick2, session_id) |
||||
assert dkgc1.on_dkg_finalized(self.nick3, session_id) |
||||
|
||||
|
||||
class FROSTClientTestCase(DKGClientTestCaseBase): |
||||
|
||||
async def asyncSetUp(self): |
||||
await super().asyncSetUp() |
||||
self.dkg_session_id = await populate_dkg_session(self) |
||||
self.fc1 = FROSTClient(self.wlt_svc1) |
||||
self.fc2 = FROSTClient(self.wlt_svc2) |
||||
self.fc3 = FROSTClient(self.wlt_svc3) |
||||
self.fc4 = FROSTClient(self.wlt_svc4) |
||||
|
||||
async def test_frost_init(self): |
||||
msg_bytes = bytes.fromhex('aabb'*16) |
||||
# test wallet with unknown hostpubkey |
||||
hostpubkeyhash_hex, session_id, sig_hex = self.fc4.frost_init( |
||||
self.dkg_session_id, msg_bytes) |
||||
assert hostpubkeyhash_hex is None |
||||
assert session_id is None |
||||
assert sig_hex is None |
||||
|
||||
hostpubkeyhash_hex, session_id, sig_hex = self.fc1.frost_init( |
||||
self.dkg_session_id, msg_bytes) |
||||
assert hostpubkeyhash_hex and len(hostpubkeyhash_hex) == 64 |
||||
assert session_id and len(session_id) == 32 |
||||
assert sig_hex and len(sig_hex) == 128 |
||||
|
||||
async def test_on_frost_init(self): |
||||
msg_bytes = bytes.fromhex('aabb'*16) |
||||
hostpubkeyhash_hex, session_id, sig_hex = self.fc1.frost_init( |
||||
self.dkg_session_id, msg_bytes) |
||||
|
||||
# fail with wrong pubkeyhash |
||||
hostpubkeyhash4_hex = sha256(self.hostpubkey4).digest() |
||||
( |
||||
nick1, |
||||
hostpubkeyhash2_hex, |
||||
session_id2_hex, |
||||
sig2_hex, |
||||
pub_nonce |
||||
) = self.fc2.on_frost_init( |
||||
self.nick1, hostpubkeyhash4_hex, session_id, sig_hex) |
||||
for v in [nick1, hostpubkeyhash2_hex, |
||||
session_id2_hex, sig2_hex, pub_nonce]: |
||||
assert v is None |
||||
|
||||
# fail with wrong sig |
||||
( |
||||
nick1, |
||||
hostpubkeyhash2_hex, |
||||
session_id2_hex, |
||||
sig2_hex, |
||||
pub_nonce |
||||
) = self.fc2.on_frost_init( |
||||
self.nick1, hostpubkeyhash_hex, session_id, '01020304'*16) |
||||
for v in [nick1, hostpubkeyhash2_hex, |
||||
session_id2_hex, sig2_hex, pub_nonce]: |
||||
assert v is None |
||||
|
||||
( |
||||
nick1, |
||||
hostpubkeyhash2_hex, |
||||
session_id2_hex, |
||||
sig2_hex, |
||||
pub_nonce |
||||
) = self.fc2.on_frost_init( |
||||
self.nick1, hostpubkeyhash_hex, session_id, sig_hex) |
||||
assert nick1 == self.nick1 |
||||
assert hostpubkeyhash2_hex and len(hostpubkeyhash2_hex) == 64 |
||||
assert session_id2_hex and len(session_id2_hex) == 64 |
||||
assert bytes.fromhex(session_id2_hex) == session_id |
||||
assert sig_hex and len(sig_hex) == 128 |
||||
assert pub_nonce and len(pub_nonce) == 66 |
||||
|
||||
# fail on second call with right params |
||||
( |
||||
nick1, |
||||
hostpubkeyhash2_hex, |
||||
session_id2_hex, |
||||
sig2_hex, |
||||
pub_nonce |
||||
) = self.fc2.on_frost_init( |
||||
self.nick1, hostpubkeyhash_hex, session_id, sig_hex) |
||||
for v in [nick1, hostpubkeyhash2_hex, |
||||
session_id2_hex, sig2_hex, pub_nonce]: |
||||
assert v is None |
||||
|
||||
def test_frost_round1(self): |
||||
msg_bytes = bytes.fromhex('aabb'*16) |
||||
hostpubkeyhash_hex, session_id, sig_hex = self.fc1.frost_init( |
||||
self.dkg_session_id, msg_bytes) |
||||
|
||||
( |
||||
nick1, |
||||
hostpubkeyhash2_hex, |
||||
session_id2_hex, |
||||
sig2_hex, |
||||
pub_nonce |
||||
) = self.fc2.on_frost_init( |
||||
self.nick1, hostpubkeyhash_hex, session_id, sig_hex) |
||||
|
||||
# fail with unknown session_id |
||||
pub_nonce = self.fc2.party_step1(b'\x05'*32) |
||||
assert pub_nonce is None |
||||
|
||||
# fail with session.sec_nonce already set |
||||
pub_nonce = self.fc2.frost_round1(session_id) |
||||
assert pub_nonce is None |
||||
|
||||
session = self.fc2.frost_sessions.get(session_id) |
||||
session.sec_nonce = None |
||||
pub_nonce = self.fc2.frost_round1(session_id) |
||||
assert pub_nonce and len(pub_nonce) == 66 |
||||
|
||||
def test_on_frost_round1(self): |
||||
msg_bytes = bytes.fromhex('aabb'*16) |
||||
hostpubkeyhash_hex, session_id, sig_hex = self.fc1.frost_init( |
||||
self.dkg_session_id, msg_bytes) |
||||
|
||||
( |
||||
nick1, |
||||
hostpubkeyhash2_hex, |
||||
session_id2_hex, |
||||
sig2_hex, |
||||
pub_nonce2 |
||||
) = self.fc2.on_frost_init( |
||||
self.nick1, hostpubkeyhash_hex, session_id, sig_hex) |
||||
|
||||
( |
||||
nick1, |
||||
hostpubkeyhash3_hex, |
||||
session_id3_hex, |
||||
sig3_hex, |
||||
pub_nonce3 |
||||
) = self.fc3.on_frost_init( |
||||
self.nick1, hostpubkeyhash_hex, session_id, sig_hex) |
||||
|
||||
# unknown session_id |
||||
( |
||||
ready_list, |
||||
nonce_agg, |
||||
dkg_session_id, |
||||
ids, |
||||
msg |
||||
) = self.fc1.on_frost_round1( |
||||
self.nick2, hostpubkeyhash2_hex, b'\xaa'*32, |
||||
sig2_hex, pub_nonce2) |
||||
for v in [ready_list, nonce_agg, dkg_session_id, ids, msg]: |
||||
assert v is None |
||||
|
||||
# unknown pubkeyhash |
||||
( |
||||
ready_list, |
||||
nonce_agg, |
||||
dkg_session_id, |
||||
ids, |
||||
msg |
||||
) = self.fc1.on_frost_round1( |
||||
self.nick2, 'bb'*32, session_id, sig2_hex, pub_nonce2) |
||||
for v in [ready_list, nonce_agg, dkg_session_id, ids, msg]: |
||||
assert v is None |
||||
|
||||
# wrong sig |
||||
( |
||||
ready_list, |
||||
nonce_agg, |
||||
dkg_session_id, |
||||
ids, |
||||
msg |
||||
) = self.fc1.on_frost_round1( |
||||
self.nick2, hostpubkeyhash2_hex, session_id, '1234'*32, pub_nonce2) |
||||
for v in [ready_list, nonce_agg, dkg_session_id, ids, msg]: |
||||
assert v is None |
||||
|
||||
( |
||||
ready_list, |
||||
nonce_agg, |
||||
dkg_session_id, |
||||
ids, |
||||
msg |
||||
) = self.fc1.on_frost_round1( |
||||
self.nick2, hostpubkeyhash2_hex, session_id, |
||||
sig2_hex, pub_nonce2) |
||||
assert ready_list == set([self.nick2]) |
||||
assert nonce_agg and len(nonce_agg)== 66 |
||||
assert dkg_session_id and dkg_session_id == self.dkg_session_id |
||||
assert ids == [1, 2] |
||||
assert msg and len(msg) == 32 and msg == msg_bytes |
||||
|
||||
# miminum pub_nonce set already presented, ignoring additional |
||||
( |
||||
ready_list, |
||||
nonce_agg, |
||||
dkg_session_id, |
||||
ids, |
||||
msg |
||||
) = self.fc1.on_frost_round1( |
||||
self.nick3, hostpubkeyhash3_hex, session_id, sig3_hex, pub_nonce3) |
||||
for v in [ready_list, nonce_agg, dkg_session_id, ids, msg]: |
||||
assert v is None |
||||
|
||||
def test_frost_agg1(self): |
||||
msg_bytes = bytes.fromhex('aabb'*16) |
||||
hostpubkeyhash_hex, session_id, sig_hex = self.fc1.frost_init( |
||||
self.dkg_session_id, msg_bytes) |
||||
|
||||
( |
||||
nick1, |
||||
hostpubkeyhash2_hex, |
||||
session_id2_hex, |
||||
sig2_hex, |
||||
pub_nonce2 |
||||
) = self.fc2.on_frost_init( |
||||
self.nick1, hostpubkeyhash_hex, session_id, sig_hex) |
||||
|
||||
( |
||||
ready_list, |
||||
nonce_agg, |
||||
dkg_session_id, |
||||
ids, |
||||
msg |
||||
) = self.fc1.on_frost_round1( |
||||
self.nick2, hostpubkeyhash2_hex, session_id, |
||||
sig2_hex, pub_nonce2) |
||||
|
||||
# fail on unknown session_id |
||||
( |
||||
nonce_agg, |
||||
dkg_session_id, |
||||
ids, |
||||
msg |
||||
) = self.fc1.frost_agg1(b'\xaa'*32) |
||||
for v in [nonce_agg, dkg_session_id, ids, msg]: |
||||
assert v is None |
||||
|
||||
# fail with coordinator.nonce_agg already set |
||||
( |
||||
nonce_agg, |
||||
dkg_session_id, |
||||
ids, |
||||
msg |
||||
) = self.fc1.frost_agg1(session_id) |
||||
for v in [nonce_agg, dkg_session_id, ids, msg]: |
||||
assert v is None |
||||
|
||||
coordinator = self.fc1.frost_coordinators.get(session_id) |
||||
coordinator.nonce_agg = None |
||||
( |
||||
nonce_agg, |
||||
dkg_session_id, |
||||
ids, |
||||
msg |
||||
) = self.fc1.frost_agg1(session_id) |
||||
assert nonce_agg and len(nonce_agg)== 66 |
||||
assert dkg_session_id and dkg_session_id == self.dkg_session_id |
||||
assert ids == [1, 2] |
||||
assert msg and len(msg) == 32 and msg == msg_bytes |
||||
|
||||
def test_frost_round2(self): |
||||
msg_bytes = bytes.fromhex('aabb'*16) |
||||
hostpubkeyhash_hex, session_id, sig_hex = self.fc1.frost_init( |
||||
self.dkg_session_id, msg_bytes) |
||||
|
||||
( |
||||
nick1, |
||||
hostpubkeyhash2_hex, |
||||
session_id2_hex, |
||||
sig2_hex, |
||||
pub_nonce2 |
||||
) = self.fc2.on_frost_init( |
||||
self.nick1, hostpubkeyhash_hex, session_id, sig_hex) |
||||
|
||||
( |
||||
ready_list, |
||||
nonce_agg, |
||||
dkg_session_id, |
||||
ids, |
||||
msg |
||||
) = self.fc1.on_frost_round1( |
||||
self.nick2, hostpubkeyhash2_hex, session_id, |
||||
sig2_hex, pub_nonce2) |
||||
|
||||
# fail on unknown session_id |
||||
partial_sig = self.fc2.frost_round2( |
||||
b'\xaa'*32, nonce_agg, self.dkg_session_id, ids, msg) |
||||
|
||||
# fail on unknown dkg_session_id |
||||
partial_sig = self.fc2.frost_round2( |
||||
session_id, nonce_agg, b'\xdd'*32, ids, msg) |
||||
|
||||
partial_sig = self.fc2.frost_round2( |
||||
session_id, nonce_agg, self.dkg_session_id, ids, msg) |
||||
assert partial_sig and len(partial_sig) == 32 |
||||
|
||||
# session.partial_sig already set |
||||
partial_sig = self.fc2.frost_round2( |
||||
session_id, nonce_agg, self.dkg_session_id, ids, msg) |
||||
assert partial_sig is None |
||||
|
||||
def test_on_frost_round2(self): |
||||
msg_bytes = bytes.fromhex('aabb'*16) |
||||
hostpubkeyhash_hex, session_id, sig_hex = self.fc1.frost_init( |
||||
self.dkg_session_id, msg_bytes) |
||||
|
||||
( |
||||
nick1, |
||||
hostpubkeyhash2_hex, |
||||
session_id2_hex, |
||||
sig2_hex, |
||||
pub_nonce2 |
||||
) = self.fc2.on_frost_init( |
||||
self.nick1, hostpubkeyhash_hex, session_id, sig_hex) |
||||
|
||||
( |
||||
ready_list, |
||||
nonce_agg, |
||||
dkg_session_id, |
||||
ids, |
||||
msg |
||||
) = self.fc1.on_frost_round1( |
||||
self.nick2, hostpubkeyhash2_hex, session_id, |
||||
sig2_hex, pub_nonce2) |
||||
|
||||
partial_sig = self.fc2.frost_round2( |
||||
session_id, nonce_agg, self.dkg_session_id, ids, msg) |
||||
|
||||
# unknown party nick |
||||
sig = self.fc1.on_frost_round2(self.nick4, session_id, partial_sig) |
||||
assert sig is None |
||||
|
||||
# party pubkey for nick3 not found |
||||
coordinator = self.fc1.frost_coordinators.get(session_id) |
||||
session2 = coordinator.sessions.pop(self.hostpubkey2) |
||||
sig = self.fc1.on_frost_round2(self.nick2, session_id, partial_sig) |
||||
assert sig is None |
||||
coordinator.sessions[self.hostpubkey2] = session2 |
||||
|
||||
# fail on unknown session_id |
||||
sig = self.fc1.on_frost_round2(self.nick2, b'\xaa'*32, partial_sig) |
||||
assert sig is None |
||||
|
||||
sig = self.fc1.on_frost_round2(self.nick2, session_id, partial_sig) |
||||
assert sig and len(sig) == 64 |
||||
|
||||
# partial_sig already set in coordinator |
||||
sig = self.fc1.on_frost_round2(self.nick2, session_id, partial_sig) |
||||
assert sig is None |
||||
@ -0,0 +1,341 @@
|
||||
# -*- coding: utf-8 -*- |
||||
|
||||
import asyncio |
||||
import base64 |
||||
import time |
||||
from pprint import pprint |
||||
|
||||
from unittest import IsolatedAsyncioTestCase |
||||
|
||||
import jmclient # install asyncioreactor |
||||
from twisted.internet import reactor |
||||
|
||||
import pytest |
||||
|
||||
import jmbitcoin as btc |
||||
from jmbase import get_log |
||||
from jmclient import ( |
||||
load_test_config, jm_single, get_network, cryptoengine, VolatileStorage, |
||||
FrostWallet, WalletService) |
||||
from jmclient import FrostIPCServer, FrostIPCClient |
||||
from jmclient.frost_clients import FROSTClient |
||||
|
||||
from test_frost_clients import populate_dkg_session |
||||
|
||||
|
||||
pytestmark = pytest.mark.usefixtures("setup_regtest_frost_bitcoind") |
||||
|
||||
log = get_log() |
||||
|
||||
|
||||
async def get_populated_wallet(entropy=None): |
||||
storage = VolatileStorage() |
||||
dkg_storage = VolatileStorage() |
||||
recovery_storage = VolatileStorage() |
||||
FrostWallet.initialize(storage, dkg_storage, recovery_storage, |
||||
get_network(), entropy=entropy) |
||||
wlt = FrostWallet(storage, dkg_storage, recovery_storage) |
||||
await wlt.async_init(storage) |
||||
return wlt |
||||
|
||||
|
||||
class DummyFrostJMClientProtocol: |
||||
|
||||
def __init__(self, factory, client, nick): |
||||
self.nick = nick |
||||
self.factory = factory |
||||
self.client = client |
||||
self.party_clients = {} |
||||
|
||||
async def dkg_gen(self): |
||||
log.debug(f'Coordinator call dkg_gen') |
||||
client = self.factory.client |
||||
md_type_idx = None |
||||
session_id = None |
||||
session = None |
||||
|
||||
while True: |
||||
if md_type_idx is None: |
||||
md_type_idx = await client.dkg_gen() |
||||
if md_type_idx is None: |
||||
log.debug('finished dkg_gen execution') |
||||
break |
||||
|
||||
if session_id is None: |
||||
session_id, _, session = self.dkg_init(*md_type_idx) |
||||
if session_id is None: |
||||
log.warn('could not get session_id from dkg_init}') |
||||
await asyncio.sleep(5) |
||||
continue |
||||
|
||||
pub = await client.wait_on_dkg_output(session_id) |
||||
if not pub: |
||||
session_id = None |
||||
session = None |
||||
continue |
||||
|
||||
if session.dkg_output: |
||||
md_type_idx = None |
||||
session_id = None |
||||
session = None |
||||
client.dkg_gen_list.pop(0) |
||||
continue |
||||
|
||||
def dkg_init(self, mixdepth, address_type, index): |
||||
log.debug(f'Coordinator call dkg_init ' |
||||
f'({mixdepth}, {address_type}, {index})') |
||||
client = self.factory.client |
||||
hostpubkeyhash, session_id, sig = client.dkg_init( |
||||
mixdepth, address_type, index) |
||||
coordinator = client.dkg_coordinators.get(session_id) |
||||
session = client.dkg_sessions.get(session_id) |
||||
if session_id and session and coordinator: |
||||
session.dkg_init_sec = time.time() |
||||
|
||||
for _, pc in self.party_clients.items(): |
||||
|
||||
async def on_dkg_init(pc, nick, hostpubkeyhash, |
||||
session_id, sig): |
||||
await pc.on_dkg_init( |
||||
nick, hostpubkeyhash, session_id, sig) |
||||
|
||||
asyncio.create_task(on_dkg_init( |
||||
pc, self.nick, hostpubkeyhash, session_id, sig)) |
||||
return session_id, coordinator, session |
||||
return None, None, None |
||||
|
||||
async def on_dkg_init(self, nick, hostpubkeyhash, session_id, sig): |
||||
client = self.factory.client |
||||
nick, hostpubkeyhash, session_id, sig, pmsg1 = client.on_dkg_init( |
||||
nick, hostpubkeyhash, session_id, sig) |
||||
if pmsg1: |
||||
pc = self.party_clients[nick] |
||||
session_id = bytes.fromhex(session_id) |
||||
await pc.on_dkg_pmsg1( |
||||
self.nick, hostpubkeyhash, session_id, sig, pmsg1) |
||||
|
||||
async def on_dkg_pmsg1(self, nick, hostpubkeyhash, session_id, sig, pmsg1): |
||||
client = self.factory.client |
||||
pmsg1 = client.deserialize_pmsg1(pmsg1) |
||||
ready_nicks, cmsg1 = client.on_dkg_pmsg1( |
||||
nick, hostpubkeyhash, session_id, sig, pmsg1) |
||||
if ready_nicks and cmsg1: |
||||
for party_nick in ready_nicks: |
||||
pc = self.party_clients[party_nick] |
||||
await pc.on_dkg_cmsg1(self.nick, session_id, cmsg1) |
||||
|
||||
async def on_dkg_cmsg1(self, nick, session_id, cmsg1): |
||||
client = self.factory.client |
||||
session = client.dkg_sessions.get(session_id) |
||||
if not session: |
||||
log.error(f'on_dkg_cmsg1: session {session_id} not found') |
||||
return {'accepted': True} |
||||
if session and session.coord_nick == nick: |
||||
cmsg1 = client.deserialize_cmsg1(cmsg1) |
||||
pmsg2 = client.party_step2(session_id, cmsg1) |
||||
if pmsg2: |
||||
pc = self.party_clients[nick] |
||||
await pc.on_dkg_pmsg2(self.nick, session_id, pmsg2) |
||||
else: |
||||
log.error(f'on_dkg_cmsg1: not coordinator nick {nick}') |
||||
|
||||
async def on_dkg_pmsg2(self, nick, session_id, pmsg2): |
||||
client = self.factory.client |
||||
pmsg2 = client.deserialize_pmsg2(pmsg2) |
||||
ready_nicks, cmsg2, ext_recovery = client.on_dkg_pmsg2( |
||||
nick, session_id, pmsg2) |
||||
if ready_nicks and cmsg2 and ext_recovery: |
||||
for party_nick in ready_nicks: |
||||
pc = self.party_clients[party_nick] |
||||
await pc.on_dkg_cmsg2( |
||||
self.nick, session_id, cmsg2, ext_recovery) |
||||
|
||||
async def on_dkg_cmsg2(self, nick, session_id, cmsg2, ext_recovery): |
||||
client = self.factory.client |
||||
session = client.dkg_sessions.get(session_id) |
||||
if not session: |
||||
log.error(f'on_dkg_cmsg2: session {session_id} not found') |
||||
return {'accepted': True} |
||||
if session and session.coord_nick == nick: |
||||
cmsg2 = client.deserialize_cmsg2(cmsg2) |
||||
finalized = client.finalize(session_id, cmsg2, ext_recovery) |
||||
if finalized: |
||||
pc = self.party_clients[nick] |
||||
await pc.on_dkg_finalized(self.nick, session_id) |
||||
else: |
||||
log.error(f'on_dkg_cmsg2: not coordinator nick {nick}') |
||||
|
||||
async def on_dkg_finalized(self, nick, session_id): |
||||
client = self.factory.client |
||||
log.debug(f'Coordinator get dkgfinalized') |
||||
client.on_dkg_finalized(nick, session_id) |
||||
|
||||
def frost_init(self, dkg_session_id, msg_bytes): |
||||
log.debug(f'Coordinator call frost_init') |
||||
client = self.factory.client |
||||
hostpubkeyhash, session_id, sig = client.frost_init( |
||||
dkg_session_id, msg_bytes) |
||||
coordinator = client.frost_coordinators.get(session_id) |
||||
session = client.frost_sessions.get(session_id) |
||||
if session_id and session and coordinator: |
||||
coordinator.frost_init_sec = time.time() |
||||
for _, pc in self.party_clients.items(): |
||||
|
||||
async def on_frost_init(pc, nick, hostpubkeyhash, |
||||
session_id, sig): |
||||
await pc.on_frost_init( |
||||
nick, hostpubkeyhash, session_id, sig) |
||||
|
||||
asyncio.create_task(on_frost_init( |
||||
pc, self.nick, hostpubkeyhash, session_id, sig)) |
||||
return session_id, coordinator, session |
||||
|
||||
async def on_frost_init(self, nick, hostpubkeyhash, session_id, sig): |
||||
client = self.factory.client |
||||
( |
||||
nick, |
||||
hostpubkeyhash, |
||||
session_id, |
||||
sig, |
||||
pub_nonce |
||||
) = client.on_frost_init(nick, hostpubkeyhash, session_id, sig) |
||||
if pub_nonce: |
||||
pc = self.party_clients[nick] |
||||
session_id = bytes.fromhex(session_id) |
||||
await pc.on_frost_round1( |
||||
self.nick, hostpubkeyhash, session_id, sig, pub_nonce) |
||||
|
||||
async def on_frost_round1(self, nick, hostpubkeyhash, |
||||
session_id, sig, pub_nonce): |
||||
client = self.factory.client |
||||
( |
||||
ready_nicks, |
||||
nonce_agg, |
||||
dkg_session_id, |
||||
ids, |
||||
msg |
||||
) = client.on_frost_round1( |
||||
nick, hostpubkeyhash, session_id, sig, pub_nonce) |
||||
if ready_nicks and nonce_agg: |
||||
for party_nick in ready_nicks: |
||||
pc = self.party_clients[nick] |
||||
self.frost_agg1(pc, self.nick, session_id, nonce_agg, |
||||
dkg_session_id, ids, msg) |
||||
|
||||
def frost_agg1(self, pc, nick, session_id, |
||||
nonce_agg, dkg_session_id, ids, msg): |
||||
pc.on_frost_agg1( |
||||
self.nick, session_id, nonce_agg, dkg_session_id, ids, msg) |
||||
|
||||
def on_frost_agg1(self, nick, session_id, |
||||
nonce_agg, dkg_session_id, ids, msg): |
||||
client = self.factory.client |
||||
session = client.frost_sessions.get(session_id) |
||||
if not session: |
||||
log.error(f'on_frost_agg1: session {session_id} not found') |
||||
return |
||||
if session and session.coord_nick == nick: |
||||
partial_sig = client.frost_round2( |
||||
session_id, nonce_agg, dkg_session_id, ids, msg) |
||||
if partial_sig: |
||||
pc = self.party_clients[nick] |
||||
pc.on_frost_round2(self.nick, session_id, partial_sig) |
||||
else: |
||||
log.error(f'on_frost_agg1: not coordinator nick {nick}') |
||||
|
||||
def on_frost_round2(self, nick, session_id, partial_sig): |
||||
client = self.factory.client |
||||
sig = client.on_frost_round2(nick, session_id, partial_sig) |
||||
if sig: |
||||
log.debug(f'Successfully get signature {sig.hex()[:8]}...') |
||||
|
||||
|
||||
class DummyFrostJMClientProtocolFactory: |
||||
|
||||
protocol = DummyFrostJMClientProtocol |
||||
|
||||
def __init__(self, client, nick): |
||||
self.client = client |
||||
self.proto_client = self.protocol(self, self.client, nick) |
||||
|
||||
def add_party_client(self, nick, party_client): |
||||
self.proto_client.party_clients[nick] = party_client |
||||
|
||||
def getClient(self): |
||||
return self.proto_client |
||||
|
||||
|
||||
class FrostIPCTestCaseBase(IsolatedAsyncioTestCase): |
||||
|
||||
def setUp(self): |
||||
load_test_config(config_path='./test_frost') |
||||
btc.select_chain_params("bitcoin/regtest") |
||||
cryptoengine.BTC_P2TR.VBYTE = 100 |
||||
jm_single().bc_interface.tick_forward_chain_interval = 2 |
||||
|
||||
async def asyncSetUp(self): |
||||
self.nick1, self.nick2, self.nick3 = ['nick1', 'nick2', 'nick3'] |
||||
entropy1 = bytes.fromhex('8e5e5677fb302874a607b63ad03ba434') |
||||
entropy2 = bytes.fromhex('38dfa80fbb21b32b2b2740e00a47de9d') |
||||
entropy3 = bytes.fromhex('3ad9c77fcd1d537b6ef396952d1221a0') |
||||
self.wlt1 = await get_populated_wallet(entropy1) |
||||
self.wlt_svc1 = WalletService(self.wlt1) |
||||
self.fc1 = FROSTClient(self.wlt_svc1) |
||||
cfactory1 = DummyFrostJMClientProtocolFactory(self.fc1, self.nick1) |
||||
self.wlt1.set_client_factory(cfactory1) |
||||
|
||||
self.wlt2 = await get_populated_wallet(entropy2) |
||||
self.wlt_svc2 = WalletService(self.wlt2) |
||||
self.fc2 = FROSTClient(self.wlt_svc2) |
||||
cfactory2 = DummyFrostJMClientProtocolFactory(self.fc2, self.nick2) |
||||
self.wlt2.set_client_factory(cfactory2) |
||||
|
||||
self.wlt3 = await get_populated_wallet(entropy3) |
||||
self.wlt_svc3 = WalletService(self.wlt3) |
||||
self.fc3 = FROSTClient(self.wlt_svc3) |
||||
cfactory3 = DummyFrostJMClientProtocolFactory(self.fc3, self.nick3) |
||||
self.wlt3.set_client_factory(cfactory3) |
||||
|
||||
cfactory1.add_party_client(self.nick2, cfactory2.proto_client) |
||||
cfactory1.add_party_client(self.nick3, cfactory3.proto_client) |
||||
|
||||
cfactory2.add_party_client(self.nick1, cfactory1.proto_client) |
||||
cfactory2.add_party_client(self.nick3, cfactory3.proto_client) |
||||
|
||||
cfactory3.add_party_client(self.nick1, cfactory1.proto_client) |
||||
cfactory3.add_party_client(self.nick2, cfactory2.proto_client) |
||||
|
||||
await populate_dkg_session(self) |
||||
|
||||
self.ipcs = FrostIPCServer(self.wlt1) |
||||
await self.ipcs.async_init() |
||||
self.ipcc = FrostIPCClient(self.wlt1) |
||||
await self.ipcc.async_init() |
||||
self.wlt1.set_ipc_client(self.ipcc) |
||||
|
||||
|
||||
class FrostIPCClientTestCase(FrostIPCTestCaseBase): |
||||
|
||||
async def asyncSetUp(self): |
||||
await super().asyncSetUp() |
||||
self.serve_task = asyncio.create_task(self.ipcs.serve_forever()) |
||||
|
||||
async def asyncTearDown(self): |
||||
self.serve_task.cancel("cancel from asyncTearDown") |
||||
|
||||
async def test_get_dkg_pubkey(self): |
||||
pubkey = await self.ipcc.get_dkg_pubkey(0, 0, 0) |
||||
dkg = self.wlt1.dkg |
||||
pubkeys = list(dkg._dkg_pubkey.values()) |
||||
assert pubkey and pubkey in pubkeys |
||||
|
||||
pubkey = await self.ipcc.get_dkg_pubkey(0, 0, 1) |
||||
pubkeys = list(dkg._dkg_pubkey.values()) |
||||
assert pubkey and pubkey in pubkeys |
||||
|
||||
async def test_frost_sign(self): |
||||
sighash = bytes.fromhex('01020304'*8) |
||||
sig, pubkey, tweaked_pubkey = await self.ipcc.frost_sign(0, 0, 0, sighash) |
||||
assert sig and len(sig) == 64 |
||||
assert pubkey and len(pubkey) == 33 |
||||
assert tweaked_pubkey and len(tweaked_pubkey) == 33 |
||||
@ -0,0 +1,410 @@
|
||||
'''Wallet functionality tests.''' |
||||
import os |
||||
import json |
||||
from pprint import pprint |
||||
|
||||
from unittest import IsolatedAsyncioTestCase |
||||
|
||||
import bencoder |
||||
|
||||
import jmclient # install asyncioreactor |
||||
from twisted.internet import reactor |
||||
|
||||
import pytest |
||||
import jmbitcoin as btc |
||||
from jmbase import get_log |
||||
from jmclient import ( |
||||
load_test_config, jm_single, VolatileStorage, get_network, cryptoengine, |
||||
create_wallet, open_test_wallet_maybe, FrostWallet, DKGManager) |
||||
|
||||
from jmfrost.chilldkg_ref.chilldkg import DKGOutput, hostpubkey_gen |
||||
from jmclient.frost_clients import ( |
||||
serialize_ext_recovery, decrypt_ext_recovery) |
||||
|
||||
pytestmark = pytest.mark.usefixtures("setup_regtest_frost_bitcoind") |
||||
|
||||
test_create_wallet_filename = "frost_testwallet_for_create_wallet_test" |
||||
|
||||
log = get_log() |
||||
|
||||
|
||||
async def get_populated_wallet(): |
||||
storage = VolatileStorage() |
||||
dkg_storage = VolatileStorage() |
||||
recovery_storage = VolatileStorage() |
||||
FrostWallet.initialize(storage, dkg_storage, recovery_storage, |
||||
get_network()) |
||||
wallet = FrostWallet(storage, dkg_storage, recovery_storage) |
||||
await wallet.async_init(storage) |
||||
return wallet |
||||
|
||||
|
||||
def populate_dkg(wlt, add_party=True, add_coordinator=True, save_dkg=True): |
||||
pubkey = hostpubkey_gen(wlt._hostseckey[:32]) |
||||
md_type_idx = (0, 0, 0) # mixdepth, address_type, index |
||||
ext_recovery_bytes = serialize_ext_recovery(*md_type_idx) |
||||
ext_recovery = btc.ecies_encrypt(ext_recovery_bytes, pubkey) |
||||
if add_party: |
||||
wlt.dkg.add_party_data( |
||||
session_id=bytes.fromhex('aa'*32), |
||||
dkg_output=DKGOutput( |
||||
bytes.fromhex('01'*32), # secshare |
||||
bytes.fromhex('02'*32 + '01'), # threshold_pubkey |
||||
[ # pubshares |
||||
bytes.fromhex('03'*32 + '02'), |
||||
bytes.fromhex('03'*32 + '03'), |
||||
bytes.fromhex('03'*32 + '04'), |
||||
] |
||||
), |
||||
hostpubkeys=[ |
||||
bytes.fromhex('02'*32 + '05'), |
||||
bytes.fromhex('02'*32 + '06'), |
||||
bytes.fromhex('02'*32 + '07'), |
||||
], |
||||
t=2, |
||||
recovery_data=bytes.fromhex('0102030405'*10), |
||||
ext_recovery=ext_recovery, |
||||
save_dkg=save_dkg |
||||
) |
||||
if add_coordinator: |
||||
wlt.dkg.add_coordinator_data( |
||||
session_id=bytes.fromhex('bb'*32), |
||||
dkg_output=DKGOutput( |
||||
bytes.fromhex('11'*32), # secshare |
||||
bytes.fromhex('02'*32 + '11'), # threshold_pubkey |
||||
[ # pubshares |
||||
bytes.fromhex('03'*32 + '12'), |
||||
bytes.fromhex('03'*32 + '13'), |
||||
bytes.fromhex('03'*32 + '14'), |
||||
] |
||||
), |
||||
hostpubkeys=[ |
||||
bytes.fromhex('02'*32 + '15'), |
||||
bytes.fromhex('02'*32 + '16'), |
||||
bytes.fromhex('02'*32 + '17'), |
||||
], |
||||
t=2, |
||||
recovery_data=bytes.fromhex('0102030405'*10), |
||||
ext_recovery=ext_recovery, |
||||
save_dkg=save_dkg |
||||
) |
||||
return ext_recovery |
||||
|
||||
|
||||
def check_dkg(wlt, ext_recovery, check_party=True, check_coordinator=True): |
||||
if check_party: |
||||
dkg_dict = wlt._dkg_storage.data[DKGManager.STORAGE_KEY] |
||||
assert dkg_dict[DKGManager.SECSHARE_SUBKEY] == { |
||||
b'\xaa'*32: b'\x01'*32 |
||||
} |
||||
assert dkg_dict[DKGManager.PUBSHARES_SUBKEY] == { |
||||
b'\xaa'*32: [ |
||||
bytes.fromhex('03'*32 + '02'), |
||||
bytes.fromhex('03'*32 + '03'), |
||||
bytes.fromhex('03'*32 + '04'), |
||||
] |
||||
} |
||||
assert dkg_dict[DKGManager.PUBKEY_SUBKEY] == { |
||||
b'\xaa'*32: bytes.fromhex('02'*32 + '01'), |
||||
} |
||||
assert dkg_dict[DKGManager.HOSTPUBKEYS_SUBKEY] == { |
||||
b'\xaa'*32: [ |
||||
bytes.fromhex('02'*32 + '05'), |
||||
bytes.fromhex('02'*32 + '06'), |
||||
bytes.fromhex('02'*32 + '07'), |
||||
] |
||||
} |
||||
assert dkg_dict[DKGManager.T_SUBKEY] == { |
||||
b'\xaa'*32: 2, |
||||
} |
||||
assert dkg_dict[DKGManager.SESSIONS_SUBKEY] == dict() |
||||
rec_dict = wlt._recovery_storage.data[DKGManager.RECOVERY_STORAGE_KEY] |
||||
assert rec_dict == { |
||||
b'\xaa'*32: [ |
||||
ext_recovery, |
||||
bytes.fromhex('0102030405'*10), |
||||
], |
||||
} |
||||
if check_coordinator: |
||||
ext_recovery_bytes = decrypt_ext_recovery(wlt._hostseckey, |
||||
ext_recovery) |
||||
dkg_dict = wlt._dkg_storage.data[DKGManager.STORAGE_KEY] |
||||
assert dkg_dict[DKGManager.SECSHARE_SUBKEY] == { |
||||
b'\xbb'*32: b'\x11'*32 |
||||
} |
||||
assert dkg_dict[DKGManager.PUBSHARES_SUBKEY] == { |
||||
b'\xbb'*32: [ |
||||
bytes.fromhex('03'*32 + '12'), |
||||
bytes.fromhex('03'*32 + '13'), |
||||
bytes.fromhex('03'*32 + '14'), |
||||
] |
||||
} |
||||
assert dkg_dict[DKGManager.PUBKEY_SUBKEY] == { |
||||
b'\xbb'*32: bytes.fromhex('02'*32 + '11'), |
||||
} |
||||
assert dkg_dict[DKGManager.HOSTPUBKEYS_SUBKEY] == { |
||||
b'\xbb'*32: [ |
||||
bytes.fromhex('02'*32 + '15'), |
||||
bytes.fromhex('02'*32 + '16'), |
||||
bytes.fromhex('02'*32 + '17'), |
||||
] |
||||
} |
||||
assert dkg_dict[DKGManager.T_SUBKEY] == { |
||||
b'\xbb'*32: 2, |
||||
} |
||||
assert dkg_dict[DKGManager.SESSIONS_SUBKEY] == { |
||||
ext_recovery_bytes: b'\xbb'*32 |
||||
} |
||||
rec_dict = wlt._recovery_storage.data[DKGManager.RECOVERY_STORAGE_KEY] |
||||
assert rec_dict == { |
||||
b'\xbb'*32: [ |
||||
ext_recovery, |
||||
bytes.fromhex('0102030405'*10), |
||||
], |
||||
} |
||||
|
||||
|
||||
class AsyncioTestCase(IsolatedAsyncioTestCase): |
||||
|
||||
params = { |
||||
'test_is_standard_wallet_script': [FrostWallet] |
||||
} |
||||
|
||||
def setUp(self): |
||||
load_test_config(config_path='./test_frost') |
||||
btc.select_chain_params("bitcoin/regtest") |
||||
#see note in cryptoengine.py: |
||||
cryptoengine.BTC_P2TR.VBYTE = 100 |
||||
jm_single().bc_interface.tick_forward_chain_interval = 2 |
||||
|
||||
def tearDown(self): |
||||
if os.path.exists(test_create_wallet_filename): |
||||
os.remove(test_create_wallet_filename) |
||||
dkg_filename = f'{test_create_wallet_filename}.dkg' |
||||
recovery_filename = f'{test_create_wallet_filename}.dkg_recovery' |
||||
if os.path.exists(dkg_filename): |
||||
os.remove(dkg_filename) |
||||
if os.path.exists(recovery_filename): |
||||
os.remove(recovery_filename) |
||||
|
||||
async def test_create_wallet(self): |
||||
password = b"hunter2" |
||||
wallet_name = test_create_wallet_filename |
||||
# test mainnet (we are not transacting) |
||||
btc.select_chain_params("bitcoin") |
||||
wallet = await create_wallet(wallet_name, password, 4, FrostWallet) |
||||
mnemonic = wallet.get_mnemonic_words()[0] |
||||
wallet.close() |
||||
# ensure that the wallet file created is openable with the password, |
||||
# and has the parameters that were claimed on creation: |
||||
new_wallet = await open_test_wallet_maybe( |
||||
wallet_name, "", 4, password=password, ask_for_password=False) |
||||
assert new_wallet.get_mnemonic_words()[0] == mnemonic |
||||
btc.select_chain_params("bitcoin/regtest") |
||||
|
||||
async def test_dkg_manager_initialized(self): |
||||
wlt = await get_populated_wallet() |
||||
dkg_dict = wlt._dkg_storage.data[DKGManager.STORAGE_KEY] |
||||
assert set(dkg_dict.keys()) == set([ |
||||
DKGManager.SECSHARE_SUBKEY, |
||||
DKGManager.PUBSHARES_SUBKEY, |
||||
DKGManager.PUBKEY_SUBKEY, |
||||
DKGManager.HOSTPUBKEYS_SUBKEY, |
||||
DKGManager.T_SUBKEY, |
||||
DKGManager.SESSIONS_SUBKEY, |
||||
]) |
||||
|
||||
assert dkg_dict[DKGManager.SECSHARE_SUBKEY] == dict() |
||||
assert dkg_dict[DKGManager.PUBSHARES_SUBKEY] == dict() |
||||
assert dkg_dict[DKGManager.PUBKEY_SUBKEY] == dict() |
||||
assert dkg_dict[DKGManager.HOSTPUBKEYS_SUBKEY] == dict() |
||||
assert dkg_dict[DKGManager.T_SUBKEY] == dict() |
||||
assert dkg_dict[DKGManager.SESSIONS_SUBKEY] == dict() |
||||
rec_dict = wlt._recovery_storage.data[DKGManager.RECOVERY_STORAGE_KEY] |
||||
assert rec_dict == dict() |
||||
|
||||
async def test_dkg_add_party_data(self): |
||||
wlt = await get_populated_wallet() |
||||
ext_recovery = populate_dkg(wlt, True, False) |
||||
check_dkg(wlt, ext_recovery, True, False) |
||||
|
||||
async def test_dkg_add_coordinator_data(self): |
||||
wlt = await get_populated_wallet() |
||||
ext_recovery = populate_dkg(wlt, False, True) |
||||
check_dkg(wlt, ext_recovery, False, True) |
||||
|
||||
async def test_dkg_save(self): |
||||
wlt = await get_populated_wallet() |
||||
ext_recovery = populate_dkg(wlt, True, True, save_dkg=False) |
||||
ext_recovery_bytes = decrypt_ext_recovery(wlt._hostseckey, |
||||
ext_recovery) |
||||
|
||||
saved_dkg = bencoder.bdecode(wlt._dkg_storage.file_data[8:]) |
||||
STORAGE_KEY = DKGManager.STORAGE_KEY |
||||
HOSTPUBKEYS_SUBKEY = DKGManager.HOSTPUBKEYS_SUBKEY |
||||
PUBKEY_SUBKEY = DKGManager.PUBKEY_SUBKEY |
||||
PUBSHARES_SUBKEY = DKGManager.PUBSHARES_SUBKEY |
||||
SECSHARE_SUBKEY = DKGManager.SECSHARE_SUBKEY |
||||
T_SUBKEY = DKGManager.T_SUBKEY |
||||
SESSIONS_SUBKEY = DKGManager.SESSIONS_SUBKEY |
||||
|
||||
assert saved_dkg[STORAGE_KEY][SECSHARE_SUBKEY] == dict() |
||||
assert saved_dkg[STORAGE_KEY][PUBSHARES_SUBKEY] == dict() |
||||
assert saved_dkg[STORAGE_KEY][PUBKEY_SUBKEY] == dict() |
||||
assert saved_dkg[STORAGE_KEY][HOSTPUBKEYS_SUBKEY] == dict() |
||||
assert saved_dkg[STORAGE_KEY][T_SUBKEY] == dict() |
||||
assert saved_dkg[STORAGE_KEY][SESSIONS_SUBKEY] == dict() |
||||
|
||||
saved_rec = bencoder.bdecode(wlt._recovery_storage.file_data[8:]) |
||||
assert saved_rec[b'dkg'] == dict() |
||||
|
||||
wlt.dkg.save() |
||||
|
||||
saved_dkg = bencoder.bdecode(wlt._dkg_storage.file_data[8:]) |
||||
assert set(saved_dkg[STORAGE_KEY][SECSHARE_SUBKEY].keys()) == set([ |
||||
b'\xaa'*32, |
||||
b'\xbb'*32, |
||||
]) |
||||
assert set(saved_dkg[STORAGE_KEY][PUBSHARES_SUBKEY].keys()) == set([ |
||||
b'\xaa'*32, |
||||
b'\xbb'*32, |
||||
]) |
||||
assert set(saved_dkg[STORAGE_KEY][PUBKEY_SUBKEY].keys()) == set([ |
||||
b'\xaa'*32, |
||||
b'\xbb'*32, |
||||
]) |
||||
assert set(saved_dkg[STORAGE_KEY][HOSTPUBKEYS_SUBKEY].keys()) == set([ |
||||
b'\xaa'*32, |
||||
b'\xbb'*32, |
||||
]) |
||||
assert set(saved_dkg[STORAGE_KEY][T_SUBKEY].keys()) == set([ |
||||
b'\xaa'*32, |
||||
b'\xbb'*32, |
||||
]) |
||||
assert set(saved_dkg[STORAGE_KEY][SESSIONS_SUBKEY].keys()) == set([ |
||||
ext_recovery_bytes |
||||
]) |
||||
|
||||
saved_rec = bencoder.bdecode(wlt._recovery_storage.file_data[8:]) |
||||
RECOVERY_STORAGE_KEY = DKGManager.RECOVERY_STORAGE_KEY |
||||
assert set(saved_rec[RECOVERY_STORAGE_KEY].keys()) == set([ |
||||
b'\xaa'*32, |
||||
b'\xbb'*32, |
||||
]) |
||||
|
||||
async def test_dkg_load_storage(self): |
||||
password = b"hunter2" |
||||
wlt = await create_wallet( |
||||
test_create_wallet_filename, password, 4, FrostWallet) |
||||
mnemonic = wlt.get_mnemonic_words()[0] |
||||
ext_recovery = populate_dkg(wlt, False, True) |
||||
check_dkg(wlt, ext_recovery, False, True) |
||||
wlt.save() |
||||
wlt.close() |
||||
|
||||
new_wlt = await open_test_wallet_maybe( |
||||
test_create_wallet_filename, "", 4, password=password, |
||||
ask_for_password=False, |
||||
load_dkg=True, dkg_read_only=False, read_only=True) |
||||
|
||||
dkgman = DKGManager( |
||||
new_wlt, new_wlt._dkg_storage, new_wlt._recovery_storage) |
||||
new_wlt._dkg = dkgman |
||||
check_dkg(new_wlt, ext_recovery, False, True) |
||||
|
||||
async def test_dkg_find_session(self): |
||||
wlt = await get_populated_wallet() |
||||
ext_recovery = populate_dkg(wlt, True, True) |
||||
assert wlt.dkg.find_session(0, 0, 0) == b'\xbb'*32 |
||||
assert wlt.dkg.find_session(0, 0, 1) is None |
||||
|
||||
async def test_dkg_find_dkg_pubkey(self): |
||||
wlt = await get_populated_wallet() |
||||
ext_recovery = populate_dkg(wlt, True, True) |
||||
assert wlt.dkg.find_dkg_pubkey(0, 0, 0) == b'\x02'*32 + b'\x11' |
||||
assert wlt.dkg.find_dkg_pubkey(0, 0, 1) is None |
||||
|
||||
async def test_dkg_recover(self): |
||||
assert 0 # FIXME need to add test |
||||
|
||||
async def test_dkg_ls(self): |
||||
wlt = await get_populated_wallet() |
||||
ext_recovery = populate_dkg(wlt, True, True) |
||||
ls_data = wlt.dkg.dkg_ls() |
||||
ls_title = 'DKG data:\n' |
||||
ls_title_len = len(ls_title) |
||||
assert ls_data.startswith(ls_title) |
||||
ls_data = ls_data[ls_title_len:] |
||||
ls_json = json.loads(ls_data) |
||||
assert set(ls_json.keys()) == set(['sessions', 'a'*64, 'b'*64]) |
||||
assert ls_json['sessions']['0,0,0'] == 'b'*64 |
||||
ls_json_a = ls_json['a'*64] |
||||
ls_json_b = ls_json['b'*64] |
||||
|
||||
assert ls_json_a['secshare'] == '01'*32 |
||||
assert set(ls_json_a['pubshares']) == set(['03'*32 + '02', |
||||
'03'*32 + '03', |
||||
'03'*32 + '04']) |
||||
assert ls_json_a['pubkey'] == '02'*32 + '01' |
||||
assert set(ls_json_a['hostpubkeys']) == set(['02'*32 + '05', |
||||
'02'*32 + '06', |
||||
'02'*32 + '07']) |
||||
assert ls_json_a['t'] == 2 |
||||
|
||||
assert ls_json_b['secshare'] == '11'*32 |
||||
assert set(ls_json_b['pubshares']) == set(['03'*32 + '12', |
||||
'03'*32 + '13', |
||||
'03'*32 + '14']) |
||||
assert ls_json_b['pubkey'] == '02'*32 + '11' |
||||
assert set(ls_json_b['hostpubkeys']) == set(['02'*32 + '15', |
||||
'02'*32 + '16', |
||||
'02'*32 + '17']) |
||||
assert ls_json_b['t'] == 2 |
||||
|
||||
async def test_dkg_rm(self): |
||||
wlt = await get_populated_wallet() |
||||
ext_recovery = populate_dkg(wlt, True, True) |
||||
rm_data = wlt.dkg.dkg_rm(['a'*64]) |
||||
rm_lines = rm_data.split('\n') |
||||
assert rm_lines[0] == f'dkg data for session {"a"*64} deleted' |
||||
rm_data = wlt.dkg.dkg_rm(['a'*64]) |
||||
rm_lines = rm_data.split('\n') |
||||
assert rm_lines[0] == f'not found dkg data for session {"a"*64}' |
||||
|
||||
rm_data = wlt.dkg.dkg_rm(['b'*64]) |
||||
rm_lines = rm_data.split('\n') |
||||
assert rm_lines[0] == f'dkg data for session {"b"*64} deleted' |
||||
assert rm_lines[1] == f'session data for session {"b"*64} deleted' |
||||
rm_data = wlt.dkg.dkg_rm(['b'*64]) |
||||
rm_lines = rm_data.split('\n') |
||||
assert rm_lines[0] == f'not found dkg data for session {"b"*64}' |
||||
|
||||
async def test_recdkg_ls(self): |
||||
wlt = await get_populated_wallet() |
||||
ext_recovery = populate_dkg(wlt, True, True) |
||||
ls_data = wlt.dkg.recdkg_ls() |
||||
ls_lines = ls_data.split('\n') |
||||
assert ls_lines[1] == 'Decrypted sesions:' |
||||
assert ls_lines[-2] == 'Not decrypted sesions:' |
||||
assert ls_lines[-1] == '[]' |
||||
ls_json = json.loads('\n'.join(ls_lines[2:-3])) |
||||
assert ls_json[0] == ['a'*64, '0'*12, '0102030405'*10] |
||||
assert ls_json[1] == ['b'*64, '0'*12, '0102030405'*10] |
||||
|
||||
async def test_recdkg_rm(self): |
||||
wlt = await get_populated_wallet() |
||||
ext_recovery = populate_dkg(wlt, True, True) |
||||
rm_data = wlt.dkg.recdkg_rm(['a'*64]) |
||||
rm_lines = rm_data.split('\n') |
||||
assert rm_lines[0] == f'dkg recovery data for session {"a"*64} deleted' |
||||
rm_data = wlt.dkg.recdkg_rm(['a'*64]) |
||||
rm_lines = rm_data.split('\n') |
||||
assert rm_lines[0] == \ |
||||
f'not found dkg recovery data for session {"a"*64}' |
||||
rm_data = wlt.dkg.recdkg_rm(['b'*64]) |
||||
rm_lines = rm_data.split('\n') |
||||
assert rm_lines[0] == f'dkg recovery data for session {"b"*64} deleted' |
||||
rm_data = wlt.dkg.recdkg_rm(['b'*64]) |
||||
rm_lines = rm_data.split('\n') |
||||
assert rm_lines[0] == \ |
||||
f'not found dkg recovery data for session {"b"*64}' |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,152 @@
|
||||
# Implementation of the Trusted Dealer Key Generation approach for FROST mentioned |
||||
# in https://datatracker.ietf.org/doc/draft-irtf-cfrg-frost/15/ (Appendix D). |
||||
# |
||||
# It's worth noting that this isn't the only compatible method (with BIP FROST Signing), |
||||
# there are alternative key generation methods available, such as BIP-FROST-DKG: |
||||
# https://github.com/BlockstreamResearch/bip-frost-dkg |
||||
|
||||
# todo: use the `Scalar` type like BIP-DKG? |
||||
#todo: this shows mypy error, but the file runs |
||||
|
||||
from typing import Tuple, List, NewType |
||||
import unittest |
||||
# todo: replace random module with secrets |
||||
import random |
||||
# for [1] import functions from reference |
||||
# [2] specify path for bip340 when running reference.py |
||||
# import sys, os |
||||
# script_dir = os.path.dirname(os.path.abspath(__file__)) |
||||
# parent_dir = os.path.abspath(os.path.join(script_dir, '..')) |
||||
# sys.path.append(parent_dir) |
||||
from jmfrost.frost_ref.utils.bip340 import ( |
||||
Point, n as curve_order, bytes_from_int, |
||||
point_mul, G, has_even_y, x |
||||
) |
||||
|
||||
# point on the secret polynomial, represents a signer's secret share |
||||
PolyPoint = Tuple[int, int] |
||||
# point on the secp256k1 curve, represents a signer's public share |
||||
ECPoint = Point |
||||
|
||||
# |
||||
# The following helper functions and types were copied from reference.py |
||||
# |
||||
PlainPk = NewType('PlainPk', bytes) |
||||
|
||||
def xbytes(P: Point) -> bytes: |
||||
return bytes_from_int(x(P)) |
||||
|
||||
def cbytes(P: Point) -> bytes: |
||||
a = b'\x02' if has_even_y(P) else b'\x03' |
||||
return a + xbytes(P) |
||||
|
||||
def derive_interpolating_value_internal(L: List[int], x_i: int) -> int: |
||||
num, deno = 1, 1 |
||||
for x_j in L: |
||||
if x_j == x_i: |
||||
continue |
||||
num *= x_j |
||||
deno *= (x_j - x_i) |
||||
return num * pow(deno, curve_order - 2, curve_order) % curve_order |
||||
# |
||||
# End of helper functions and types copied from reference.py. |
||||
# |
||||
|
||||
# evaluates poly using Horner's method, assuming coeff[0] corresponds |
||||
# to the coefficient of highest degree term |
||||
def polynomial_evaluate(coeffs: List[int], x: int) -> int: |
||||
res = 0 |
||||
for coeff in coeffs: |
||||
res = res * x + coeff |
||||
return res % curve_order |
||||
|
||||
|
||||
def secret_share_combine(shares: List[PolyPoint]) -> int: |
||||
x_coords = [] |
||||
for (x, y) in shares: |
||||
x_coords.append(x) |
||||
|
||||
secret = 0 |
||||
for (x, y) in shares: |
||||
delta = y * derive_interpolating_value_internal(x_coords, x) |
||||
secret += delta |
||||
return secret % curve_order |
||||
|
||||
# coeffs shouldn't include the const term (i.e. secret) |
||||
def secret_share_shard(secret: int, coeffs: List[int], max_participants: int) -> List[PolyPoint]: |
||||
coeffs = coeffs + [secret] |
||||
|
||||
secshares: List[PolyPoint] = [] |
||||
for x_i in range(1, max_participants + 1): |
||||
y_i = polynomial_evaluate(coeffs, x_i) |
||||
secshare_i = (x_i, y_i) |
||||
secshares.append(secshare_i) |
||||
return secshares |
||||
|
||||
def trusted_dealer_keygen(secret_key: int, max_participants: int, min_participants: int) -> Tuple[ECPoint, List[PolyPoint], List[ECPoint]]: |
||||
assert (1 <= secret_key <= curve_order - 1) |
||||
assert (2 <= min_participants <= max_participants) |
||||
# we don't force BIP340 compatibility of group pubkey in keygen |
||||
P = point_mul(G, secret_key) |
||||
assert P is not None |
||||
|
||||
coeffs = [] |
||||
for i in range(min_participants - 1): |
||||
coeffs.append(random.randint(1, curve_order - 1)) |
||||
secshares = secret_share_shard(secret_key, coeffs, max_participants) |
||||
pubshares = [] |
||||
for secshare in secshares: |
||||
X = point_mul(G, secshare[1]) |
||||
assert X is not None |
||||
pubshares.append(X) |
||||
return (P, secshares, pubshares) |
||||
|
||||
# Test vector from RFC draft. |
||||
# section F.5 of https://datatracker.ietf.org/doc/draft-irtf-cfrg-frost/15/ |
||||
class Tests(unittest.TestCase): |
||||
def setUp(self) -> None: |
||||
self.max_participants = 3 |
||||
self.min_participants = 2 |
||||
self.poly = [ |
||||
0xfbf85eadae3058ea14f19148bb72b45e4399c0b16028acaf0395c9b03c823579, |
||||
0x0d004150d27c3bf2a42f312683d35fac7394b1e9e318249c1bfe7f0795a83114, |
||||
] |
||||
self.shares: List[PolyPoint] = [ |
||||
(1, 0x08f89ffe80ac94dcb920c26f3f46140bfc7f95b493f8310f5fc1ea2b01f4254c), |
||||
(2, 0x04f0feac2edcedc6ce1253b7fab8c86b856a797f44d83d82a385554e6e401984), |
||||
(3, 0x00e95d59dd0d46b0e303e500b62b7ccb0e555d49f5b849f5e748c071da8c0dbc), |
||||
] |
||||
self.secret = 0x0d004150d27c3bf2a42f312683d35fac7394b1e9e318249c1bfe7f0795a83114 |
||||
|
||||
def test_polynomial_evaluate(self) -> None: |
||||
coeffs = self.poly.copy() |
||||
expected_secret = self.secret |
||||
|
||||
self.assertEqual(polynomial_evaluate(coeffs, 0), expected_secret) |
||||
|
||||
def test_secret_share_combine(self) -> None: |
||||
shares: List[PolyPoint] = self.shares.copy() |
||||
expected_secret = self.secret |
||||
|
||||
self.assertEqual(secret_share_combine([shares[0], shares[1]]), expected_secret) |
||||
self.assertEqual(secret_share_combine([shares[1], shares[2]]), expected_secret) |
||||
self.assertEqual(secret_share_combine([shares[0], shares[2]]), expected_secret) |
||||
self.assertEqual(secret_share_combine(shares), expected_secret) |
||||
|
||||
def test_trusted_dealer_keygen(self) -> None: |
||||
secret_key = random.randint(1, curve_order - 1) |
||||
max_participants = 5 |
||||
min_participants = 3 |
||||
group_pk, secshares, pubshares = trusted_dealer_keygen(secret_key, max_participants, min_participants) |
||||
|
||||
# group_pk need not be xonly (i.e., have even y always) |
||||
self.assertEqual(group_pk, point_mul(G, secret_key)) |
||||
self.assertEqual(secret_share_combine(secshares), secret_key) |
||||
self.assertEqual(len(secshares), max_participants) |
||||
self.assertEqual(len(pubshares), max_participants) |
||||
for i in range(len(pubshares)): |
||||
with self.subTest(i=i): |
||||
self.assertEqual(pubshares[i], point_mul(G, secshares[i][1])) |
||||
|
||||
if __name__=='__main__': |
||||
unittest.main() |
||||
@ -0,0 +1,154 @@
|
||||
#NOTE: This configuration file is for testing with regtest only |
||||
#For mainnet usage, running a JoinMarket script will create the default file |
||||
[DAEMON] |
||||
no_daemon = 1 |
||||
daemon_port = 27183 |
||||
daemon_host = localhost |
||||
use_ssl = false |
||||
|
||||
[BLOCKCHAIN] |
||||
blockchain_source = regtest |
||||
rpc_host = localhost |
||||
rpc_port = 18443 |
||||
rpc_user = bitcoinrpc |
||||
rpc_password = 123456abcdef |
||||
network = testnet |
||||
rpc_wallet_file = jm-test-frost-wallet |
||||
|
||||
[MESSAGING:server1] |
||||
type = irc |
||||
host = localhost |
||||
hostid = localhost1 |
||||
channel = joinmarket-pit |
||||
port = 16667 |
||||
usessl = false |
||||
socks5 = false |
||||
socks5_host = localhost |
||||
socks5_port = 9150 |
||||
|
||||
[MESSAGING:server2] |
||||
type = irc |
||||
host = localhost |
||||
hostid = localhost2 |
||||
channel = joinmarket-pit |
||||
port = 16668 |
||||
usessl = false |
||||
socks5 = false |
||||
socks5_host = localhost |
||||
socks5_port = 9150 |
||||
|
||||
[MESSAGING:onion] |
||||
# onion based message channels must have the exact type 'onion' |
||||
# (while the section name above can be MESSAGING:whatever), and there must |
||||
# be only ONE such message channel configured (note the directory servers |
||||
# can be multiple, below): |
||||
type = onion |
||||
socks5_host = localhost |
||||
socks5_port = 9050 |
||||
# the tor control configuration: |
||||
tor_control_host = localhost |
||||
# or, to use a UNIX socket |
||||
# control_host = unix:/var/run/tor/control |
||||
tor_control_port = 9051 |
||||
# the host/port actually serving the hidden service |
||||
# (note the *virtual port*, that the client uses, |
||||
# is hardcoded to 80): |
||||
onion_serving_host = 127.0.0.1 |
||||
onion_serving_port = 8080 |
||||
# This is mandatory for directory nodes (who must also set their |
||||
# own .onion:port as the only directory in directory_nodes, below), |
||||
# but NOT TO BE USED by non-directory nodes (which is you, unless |
||||
# you know otherwise!), as it will greatly degrade your privacy. |
||||
# |
||||
# Special handling on regtest, so just ignore and let the code handle it: |
||||
hidden_service_dir = "" |
||||
# This is a comma separated list (comma can be omitted if only one item). |
||||
# Each item has format host:port |
||||
# On regtest we are going to increment the port numbers served from, with |
||||
# the value used here as the starting value: |
||||
directory_nodes = localhost:8081 |
||||
# this is not present in default real config |
||||
# and is specifically used to flag tests: |
||||
# means we use indices 1,2,3,4,5: |
||||
regtest_count=1,5 |
||||
|
||||
[TIMEOUT] |
||||
maker_timeout_sec = 10 |
||||
|
||||
[LOGGING] |
||||
console_log_level = DEBUG |
||||
|
||||
[POLICY] |
||||
|
||||
segwit = true |
||||
native = true |
||||
frost = true |
||||
|
||||
# for dust sweeping, try merge_algorithm = gradual |
||||
# for more rapid dust sweeping, try merge_algorithm = greedy |
||||
# for most rapid dust sweeping, try merge_algorithm = greediest |
||||
# but don't forget to bump your miner fees! |
||||
merge_algorithm = default |
||||
# the fee estimate is based on a projection of how many satoshis |
||||
# per kB are needed to get in one of the next N blocks, N set here |
||||
# as the value of 'tx_fees'. This estimate can be extremely high |
||||
# if you set N=1, so we choose N=3 for a more reasonable figure, |
||||
# as our default. Note that for clients not using a local blockchain |
||||
# instance, we retrieve an estimate from the API at cointape.com, currently. |
||||
tx_fees = 3 |
||||
taker_utxo_retries = 3 |
||||
taker_utxo_age = 1 |
||||
taker_utxo_amtpercent = 20 |
||||
accept_commitment_broadcasts = 1 |
||||
#some settings useful for testing scenarios |
||||
#laxity for repeated tests; tests on actual |
||||
#commitments/maker limit/utxo sourcing logic should obviously reset |
||||
taker_utxo_retries = 3 |
||||
minimum_makers = 1 |
||||
listunspent_args = [0] |
||||
max_sats_freeze_reuse = -1 |
||||
|
||||
# ONLY for testing! |
||||
max_cj_fee_abs = 200000 |
||||
max_cj_fee_rel = 0.2 |
||||
|
||||
[PAYJOIN] |
||||
# for the majority of situations, the defaults |
||||
# need not be altered - they will ensure you don't pay |
||||
# a significantly higher fee. |
||||
# MODIFICATION OF THESE SETTINGS IS DISADVISED. |
||||
|
||||
# Payjoin protocol version; currently only '1' is supported. |
||||
payjoin_version = 1 |
||||
|
||||
# servers can change their destination address by default (0). |
||||
# if '1', they cannot. Note that servers can explicitly request |
||||
# that this is activated, in which case we respect that choice. |
||||
disable_output_substitution = 0 |
||||
|
||||
# "default" here indicates that we will allow the receiver to |
||||
# increase the fee we pay by: |
||||
# 1.2 * (our_fee_rate_per_vbyte * vsize_of_our_input_type) |
||||
# (see https://github.com/bitcoin/bips/blob/master/bip-0078.mediawiki#span_idfeeoutputspanFee_output) |
||||
# (and 1.2 to give breathing room) |
||||
# which indicates we are allowing roughly one extra input's fee. |
||||
# If it is instead set to an integer, then that many satoshis are allowed. |
||||
# Additionally, note that we will also set the parameter additionafeeoutputindex |
||||
# to that of our change output, unless there is none in which case this is disabled. |
||||
max_additional_fee_contribution = default |
||||
|
||||
# this is the minimum satoshis per vbyte we allow in the payjoin |
||||
# transaction; note it is decimal, not integer. |
||||
min_fee_rate = 1.1 |
||||
|
||||
# for payjoins to hidden service endpoints, the socks5 configuration: |
||||
onion_socks5_host = localhost |
||||
onion_socks5_port = 9050 |
||||
# in some exceptional case the HS may be SSL configured, |
||||
# this feature is not yet implemented in code, but here for the |
||||
# future: |
||||
hidden_service_ssl = false |
||||
|
||||
[FROST] |
||||
t = 2 |
||||
hostpubkeys = 024cc1ec6fedba593a6cb683b627953b2aa80f8df80f360f78805fc00898697c74,03a8348fe4afd1974d07a50783c5d5c1ef59200eeb8ab97c7d8606534749a7043d,0307952377783138b82b222fd73199c541338a96cf758ed5a27816d6e6a324e77d |
||||
@ -0,0 +1,150 @@
|
||||
#NOTE: This configuration file is for testing with regtest only |
||||
#For mainnet usage, running a JoinMarket script will create the default file |
||||
[DAEMON] |
||||
no_daemon = 1 |
||||
daemon_port = 27183 |
||||
daemon_host = localhost |
||||
use_ssl = false |
||||
|
||||
[BLOCKCHAIN] |
||||
blockchain_source = regtest |
||||
rpc_host = localhost |
||||
rpc_port = 18443 |
||||
rpc_user = bitcoinrpc |
||||
rpc_password = 123456abcdef |
||||
network = testnet |
||||
rpc_wallet_file = jm-test-taproot-wallet |
||||
|
||||
[MESSAGING:server1] |
||||
type = irc |
||||
host = localhost |
||||
hostid = localhost1 |
||||
channel = joinmarket-pit |
||||
port = 16667 |
||||
usessl = false |
||||
socks5 = false |
||||
socks5_host = localhost |
||||
socks5_port = 9150 |
||||
|
||||
[MESSAGING:server2] |
||||
type = irc |
||||
host = localhost |
||||
hostid = localhost2 |
||||
channel = joinmarket-pit |
||||
port = 16668 |
||||
usessl = false |
||||
socks5 = false |
||||
socks5_host = localhost |
||||
socks5_port = 9150 |
||||
|
||||
[MESSAGING:onion] |
||||
# onion based message channels must have the exact type 'onion' |
||||
# (while the section name above can be MESSAGING:whatever), and there must |
||||
# be only ONE such message channel configured (note the directory servers |
||||
# can be multiple, below): |
||||
type = onion |
||||
socks5_host = localhost |
||||
socks5_port = 9050 |
||||
# the tor control configuration: |
||||
tor_control_host = localhost |
||||
# or, to use a UNIX socket |
||||
# control_host = unix:/var/run/tor/control |
||||
tor_control_port = 9051 |
||||
# the host/port actually serving the hidden service |
||||
# (note the *virtual port*, that the client uses, |
||||
# is hardcoded to 80): |
||||
onion_serving_host = 127.0.0.1 |
||||
onion_serving_port = 8080 |
||||
# This is mandatory for directory nodes (who must also set their |
||||
# own .onion:port as the only directory in directory_nodes, below), |
||||
# but NOT TO BE USED by non-directory nodes (which is you, unless |
||||
# you know otherwise!), as it will greatly degrade your privacy. |
||||
# |
||||
# Special handling on regtest, so just ignore and let the code handle it: |
||||
hidden_service_dir = "" |
||||
# This is a comma separated list (comma can be omitted if only one item). |
||||
# Each item has format host:port |
||||
# On regtest we are going to increment the port numbers served from, with |
||||
# the value used here as the starting value: |
||||
directory_nodes = localhost:8081 |
||||
# this is not present in default real config |
||||
# and is specifically used to flag tests: |
||||
# means we use indices 1,2,3,4,5: |
||||
regtest_count=1,5 |
||||
|
||||
[TIMEOUT] |
||||
maker_timeout_sec = 10 |
||||
|
||||
[LOGGING] |
||||
console_log_level = DEBUG |
||||
|
||||
[POLICY] |
||||
|
||||
segwit = true |
||||
native = true |
||||
taproot = true |
||||
|
||||
# for dust sweeping, try merge_algorithm = gradual |
||||
# for more rapid dust sweeping, try merge_algorithm = greedy |
||||
# for most rapid dust sweeping, try merge_algorithm = greediest |
||||
# but don't forget to bump your miner fees! |
||||
merge_algorithm = default |
||||
# the fee estimate is based on a projection of how many satoshis |
||||
# per kB are needed to get in one of the next N blocks, N set here |
||||
# as the value of 'tx_fees'. This estimate can be extremely high |
||||
# if you set N=1, so we choose N=3 for a more reasonable figure, |
||||
# as our default. Note that for clients not using a local blockchain |
||||
# instance, we retrieve an estimate from the API at cointape.com, currently. |
||||
tx_fees = 3 |
||||
taker_utxo_retries = 3 |
||||
taker_utxo_age = 1 |
||||
taker_utxo_amtpercent = 20 |
||||
accept_commitment_broadcasts = 1 |
||||
#some settings useful for testing scenarios |
||||
#laxity for repeated tests; tests on actual |
||||
#commitments/maker limit/utxo sourcing logic should obviously reset |
||||
taker_utxo_retries = 3 |
||||
minimum_makers = 1 |
||||
listunspent_args = [0] |
||||
max_sats_freeze_reuse = -1 |
||||
|
||||
# ONLY for testing! |
||||
max_cj_fee_abs = 200000 |
||||
max_cj_fee_rel = 0.2 |
||||
|
||||
[PAYJOIN] |
||||
# for the majority of situations, the defaults |
||||
# need not be altered - they will ensure you don't pay |
||||
# a significantly higher fee. |
||||
# MODIFICATION OF THESE SETTINGS IS DISADVISED. |
||||
|
||||
# Payjoin protocol version; currently only '1' is supported. |
||||
payjoin_version = 1 |
||||
|
||||
# servers can change their destination address by default (0). |
||||
# if '1', they cannot. Note that servers can explicitly request |
||||
# that this is activated, in which case we respect that choice. |
||||
disable_output_substitution = 0 |
||||
|
||||
# "default" here indicates that we will allow the receiver to |
||||
# increase the fee we pay by: |
||||
# 1.2 * (our_fee_rate_per_vbyte * vsize_of_our_input_type) |
||||
# (see https://github.com/bitcoin/bips/blob/master/bip-0078.mediawiki#span_idfeeoutputspanFee_output) |
||||
# (and 1.2 to give breathing room) |
||||
# which indicates we are allowing roughly one extra input's fee. |
||||
# If it is instead set to an integer, then that many satoshis are allowed. |
||||
# Additionally, note that we will also set the parameter additionafeeoutputindex |
||||
# to that of our change output, unless there is none in which case this is disabled. |
||||
max_additional_fee_contribution = default |
||||
|
||||
# this is the minimum satoshis per vbyte we allow in the payjoin |
||||
# transaction; note it is decimal, not integer. |
||||
min_fee_rate = 1.1 |
||||
|
||||
# for payjoins to hidden service endpoints, the socks5 configuration: |
||||
onion_socks5_host = localhost |
||||
onion_socks5_port = 9050 |
||||
# in some exceptional case the HS may be SSL configured, |
||||
# this feature is not yet implemented in code, but here for the |
||||
# future: |
||||
hidden_service_ssl = false |
||||
Loading…
Reference in new issue