7.1 KiB
FROST P2TR wallet development details
NOTE: minimal python version is python3.12
FrostWallet storages
FrostWallet have two additional storages in addtion to wallet Storage:
DKGStoragewith DKG dataDKGRecoveryStoragewith DKG recovery data (unencrypted)
They are loaded only for DKG/FROST support and not loaded on usual wallet usage.
Usual wallet usage interact with FROST/DKG functionality via IPC code in
frost_ipc.py (currently AF_UNIX socket for simplicity).
jmclient.wallet_utils.open_wallet has two new parameters:
load_dkg=False: by default do not loadDKGStoragedkg_read_only=True: loadDKGStoragefor read only commands
Additionally open_wallet params read_only and dkg_read_only can not
be mutually unset by design.
Structure of DKG data in the DKGStorage
"dkg": {
"sessions": {
"md_type_idx": session_id,
...
},
"pubkey": {
"session_id": threshold_pubkey,
...
},
"pubshares": {
"session_id": [pubshare1, pubshare2, ...],
...
},
"secshare": {
"session_id": secshare,
...
},
"hostpubkeys": {
"session_id": [hostpubkey1, hostpubkey2, ...],
...
},
"t": {
"session_id": t,
...
}
}
Where md_type_idx is a serialization in bytes of mixdepth, address_type,
index of pubkey as in the HD wallet derivations.
Overall information
In the code used twisted asyncioreactor in place of standard twisted reactor.
Initialization is done as early as possible in jmclient/__init__.py.
Classes for wallets: TaprootWallet, FrostWallet in the jmclient/wallet.py
Utility class DKGManager in the jmclient/wallet.py.
Engine classes BTC_P2TR(BTCEngine), BTC_P2TR_FROST(BTC_P2TR) in the
jmclient/cryptoengine.py.
scripts/wallet-tool.py commands
hostpubkey: display host public keyservefrost: run only as DKG/FROST support (separate process which need to be run permanently)dkgrecover: recover DKG sessions from DKG recovery filedkgls: display FrostWallet DKG datadkgrm: rm FrostWallet DKG data bysession_idlistrecdkgls: display Recovery DKG File datarecdkgrm: rm Recovery DKG File data bysession_idlisttestdkg: run only as test of DKG processtestfrost: run only as test of FROST signing
Description of jmclient/frost_clients.py
class DKGClient: clent which support only DKG sessions over JM channels. Useschilldkgreference code from https://github.com/BlockstreamResearch/bip-frost-dkg/, placed in thejmfrost/chilldkg_refpackage.
Uses channel level commands dkginit, dkgpmsg1, dkgcmsg1, dkgpmsg2,
dkgcmsg2, dkgfinalized added to jmdaemon/protocol.py.
NOTE: dkgfinalized is used to ensure all DKG party saw dkgcmsg2 and
saved DKG data to wallet/recovery data.
Commands in the jmbase/commands.py: JMDKGInit, JMDKGPMsg1, JMDKGCMsg1,
JMDKGPMsg2, MDKGCMsg2, JMDKGFinalized, JMDKGInitSeen, JMDKGPMsg1Seen,
JMDKGCMsg1Seen, JMDKGPMsg2Seen, JMDKGCMsg2Seen, JMDKGFinalizedSeen.
Responders on the commands in the jmclient/client_protocol.py,
jmdaemon/daemon_protocol.py.
In the DKG sessions the party which need new pubkey is named Coordinator.
class FROSTClient(DKGClient): clent which support DKG/FROST sessions over JM channels. Uses reference FROST code from https://github.com/siv2r/bip-frost-signing/, placed in thejmfrost/frost_refpackage.
Uses channel level commands frostreq, frostack, frostinit, frostround1,
frostagg1, frostround2 added to jmdaemon/protocol.py.
Commands in the jmbase/commands.py: JMFROSTReq, JMFROSTAck,
JMFROSTInit, JMFROSTRound1, JMFROSTAgg1, JMFROSTRound2,
JMFROSTReqSeen, JMFROSTAckSeen, JMFROSTInitSeen, JMFROSTRound1Seen,
JMFROSTAgg1Seen, JMFROSTRound2Seen.
Responders on the commands in the jmclient/client_protocol.py,
jmdaemon/daemon_protocol.py.
In the FROST sessions the party which need new signature is named Coordinator.
Details on FROST message channel commands
frostreq: public broadcast command from coordinator to request encrypted FROST exchange
req_msg = f'!frostreq {hostpubkeyhash} {sig} {session_id} {dh_pubk}'
self.mcc.pubmsg(req_msg)
hostpubkeyhash: sha256 hash of wallethostpubkeyto identify wallet to other FROST partiessig: Schnorr signature onsession_idto verify withhostpubkeyto authenticate walletsession_id: random 32 bytes to identify FROST sessiondh_pubk: ECDH public key to create encrypted private messages for other FROST commands
frostack: private unencrypted command from parties to acknowledge encrypted FROST exchange
ack_msg = f'{hostpubkeyhash} {sig} {session_id} {dh_pubk}
self.mcc.prepare_privmsg(nick, 'frostack', ack_msg)
hostpubkeyhash: sha256 hash of wallethostpubkeyto identify wallet for coordinatorsig: Schnorr signature onsession_idto verify withhostpubkeyto authenticate walletsession_id: 32 bytes to idenify FROST sessiondh_pubk: ECDH public key to create encrypted private messages for other FROST commands
frostinit: private encrypted command from coordinator to initiate FROST exchange
init_msg = f'{session_id}'
self.mcc.prepare_privmsg(nick, 'frostinit', init_msg)
session_id: 32 bytes to idenify FROST session
frostround1: private encrypted command from parties to send pub_nonce
part of FROST exchange
round1_msg = f'{session_id} {hostpubkeyhash} {pub_nonce}'
self.mcc.prepare_privmsg(nick, "frostround1", round1_msg)
session_id: 32 bytes to idenify FROST sessionhostpubkeyhash: sha256 hash of wallethostpubkeyto identify wallet for coordinatorpub_nonce: public part ofsec_nonce/pub_noncepair
frostagg1: private encrypted command from coorinator to send aggregated nonces data, DKG session id to get key data, ids of sign parties and message to sign
agg1_msg = f'{session_id} {nonce_agg} {dkg_session_id} {ids} {msg}'
self.mcc.prepare_privmsg(nick, "frostagg1", agg1_msg)
session_id: 32 bytes to idenify FROST sessionnonce_agg: aggregated pub nonces datadkg_session_id: bytes to idenify DKG session where key data for FROST whas generatedids: FROST sign parties idsmsg: 32 bytes message to sign
frostround2: private encrypted command from parties to send partial signature for coordinator
msg = f'{session_id} {partial_sig}'
self.mcc.prepare_privmsg(nick, "frostround2", msg)
session_id: 32 bytes to idenify FROST sessionpartial_sig: partial FROST signature agregated later on coordinator
Recovery storage, recovery data file.
ChillDKG recovery data is placed in the unencrypted recovery file with
the name wallet.jmdat.dkg_recovery. Code of class DKGRecoveryStorage is
placed in jmclient/storage.py
Utility scripts
Currently changes in the code allow creation of unencrypted wallets, if empty password is used.
scripts/bdecode.py: allow decode wallet/recovery data files to stdout.scripts/bencode.py: allow allow encode text file to bencode format. Separate options is presented to encode with DKG data file magic or DKG recovery data file magic.