Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle compatibility w/ airgapped QRCode communicant wallet #539

Open
pythcoiner opened this issue May 24, 2023 · 38 comments
Open

Handle compatibility w/ airgapped QRCode communicant wallet #539

pythcoiner opened this issue May 24, 2023 · 38 comments

Comments

@pythcoiner
Copy link
Collaborator

I open this issue for brainstorm about the optimal workflow for signing with airgapped wallet using communication over QRCode scanning (Seedsigner/Jade/SpecterDIY/???)

I think for now only specter DIY handle miniscript ( but seedsigner also use embit python library who handle miniscript)

An issue also have been open on seedsigner project about that.
I'm interested about your thought about which way to go and which way to not go.

i'm doing some experimentation on a seedsigner, will try to show more about that in few days.

@pythcoiner
Copy link
Collaborator Author

my first draft implementation of miniscript workflow on seedsigner is available here, the flow can be summarized as:

graph TB
classDef user fill:#3c3d3e, color:#fff;
classDef test fill:#46d367, color:#000;
classDef signer fill:#7fd310, color:#000;
classDef wallet fill:#ffaa7f, color:#000;

L1(user action)
L1:::user

L2(wallet action)
L2:::wallet

L3(signer action)
L3:::signer

L4{signer\nlogic}
L4:::test

L5[signer display]

Loading
graph TB
classDef user fill:#3c3d3e, color:#fff;
classDef test fill:#46d367, color:#000;
classDef signer fill:#7fd310, color:#000;
classDef wallet fill:#ffaa7f, color:#000;

W0(Display descriptor QR)
W0:::wallet --> 

S0(Scan descriptor)
S0:::signer -->

S1{has PoR?}
S1:::test

S1 -- yes --> S10[Check policy alias] -- ACK --> S11

S1 -- no --> S20[Check policy] -->
U21(User control policy)
U21:::user -- ACK -->

S22[Save?] -- save --> S210[Display descriptor + PoR QRCode]
S210 --> W00(Scan descriptor + PoR)
W00:::wallet --> W000(store PoR)
W000:::wallet

S22 -- continue --> S11[Scan PSBT]

S210 -- continue --> S11 -->

W12(Display PSBT QR)
W12:::wallet -->

S13(Scan PSBT)
S13:::signer -->

S14[Show PSBT details] -->
U15(check psbt)
U15:::user -- ACK -->

S15(sign PSBT)
S15:::signer -->

S16[Show signed PSBT QR] -->

W17(Scan signed PSBT)
W17:::wallet -- broadcast --> W18(Broadcast)
W18:::wallet






Loading

@pythcoiner
Copy link
Collaborator Author

few remarks:

    1. I think to UX improvements we need to split this process into 2 parts:
    • A) Liana supply JSON data (descriptor w/out PoR) encoded into Multi QRCode for descriptor registration purpose
    {
       "label": "liana",
      "descr_alias": <descriptor alias>,
      "descriptor": <descriptor>,
    "por":  "",
      "keys_aliases": {
        <alias1>: <Xpub1>,
        <alias2>: <Xpub2>,
        <alias3>:<Xpub3>,
      },
    }

    here the por (Proof of Registration) field is left empty, then the signer will understand we only want to run the descriptor registration process

    • B) Liana supply JSON data (descriptor w/ PoR + PSBT) encoded into Multi QRCode for PSBT signing purpose
    {
       "label": "liana",
      "descr_alias": <descriptor alias>,
      "descriptor": <descriptor>,
      "por":  <PoR>,
      "keys_aliases": {
        <alias1>: <Xpub1>,
        <alias2>: <Xpub2>,
        <alias3>:<Xpub3>,
      },
      "psbt": <PSBT>,
    }

    For A) and B):

    • <descriptor_alias> represent the descriptor alias to be displayed by the signing device when a PoR is supplied (in this case the user doesn't need to follow the descriptor review process since it has done it once and Liana stores the PoR).
    • represent the miniscript descriptor
    • (empty in A) ) represents the Proof of Registration processed at step A) by the signing device (it is processed by hash160(seed + alias + descriptor))
    • "keys_aliases" is a dict of key:value pair where the key is the alias and the value is the Xpub, these data can be used for UX improvement on signing device side (for example display the sending path: and_v(v:pkh(Alice),older(65535)) instead and_v(v:pkh([842a626e/48'/1'/0'/2']tpubDENBboujRvpkS8SgZsrpqG2BCUBoaAc4c57jHFe1NwKAtfVjDZDUadQKYv4pkAEF2afPv6TtQ2BoYFJAPLbuKpL1usiySERZekGo4JmnWhh/<0;1>/*),older(65535)))
    1. Regarding QR data encoding, the amount of data transferred is not huge. Still, almost the air-gapped signing devices have pretty low-resolution screens & cameras, so data should be sent through dynamic multi QRCodes rather than a single 'big' QRCode, AFAIK, there is 2 encoding used by air-gapped signing devices:
    • Specter format used by Specter DIY and SeedSigner, this encoding is pretty simple and we just had to split the data in several chunks of fixed size and add a p<m>of<n> label as header.
    • Blockchain Commons UR Types format is used by almost all air-gapped signing devices (including Blockstream Jade and SeedSigner), this encoding looks quite more complex (I don't dig deep into this format implementations, but it use CBOR-encoded data structures ), and I don't know whereas there is some Rust implementation of it (SeedSigner used the Foundation and selfcustody implementations together

@pythcoiner
Copy link
Collaborator Author

pythcoiner commented Jun 18, 2023

after a quick check i found this crate implementing Blockchain Commons UR Types

@edouardparis
Copy link
Member

Thank you very much for the investigative work !

The label field usage is not clear for me. Can it be removed or is it part of a communication standard ?

If i understand correctly descriptor_alias would be equivalent of the wallet name required by a Ledger device, so as a open question: can we rename it for wallet_name or is it bad naming ?

keys_aliases seems to be a good improvement for descriptor readability but we need to be careful as device cannot trust laptop and user must verify external xpubs.

  • for descriptor registration, If I understand correctly your proposal: User will first see the template of the descriptor with the keys_aliases and then verify the dict of alias:key with hopefully the their/our labeling like ledger is doing
    Requirement for this labeling would be that Seedsigner loaded the seed before registering the descriptor, which is maybe obvious because Seedsigner needs to provide a PoR by signing the data with a key.
  • for signing process, user just want change outputs to be labelled with the descriptor alias, do we need to display the descriptor content or is it just a nice to have ? My first opinion is to have less information required with the PoR and the descriptor_alias and descriptor are enough.

@pythcoiner
Copy link
Collaborator Author

pythcoiner commented Jun 19, 2023

About the label field, I just left it there because it is used by seedsigner QRCode decoding function to determine that the data are representing a descriptor.

I just check to export some wallet descriptors from Sparrow via QRCode:

To Specter Desktop (single sig) (single QRCode):

{
  "label": "pythcoiner",
  "blockheight": <block_height>,
  "descriptor": "wpkh([<fingerprint>]<xpub>)#<checksum>"
}

To Specter Desktop (multi-sig) (single QRCode)

{
  "label": "pythcoiner",
  "blockheight": <block_height>,
  "descriptor": "wsh(sortedmulti(2,[<fingerprint>/48h/0h/1h/2h]<xpub>,[<fingerprint>/48h/0h/1h/2h]<xpub>))#<checksum>"
}

To Specter DIY (multi-sig) (single QRCode):

addwallet pythcoiner&wsh(sortedmulti(2,[<fingerprint>/48h/0h/1h/2h]<xpub>,[<fingerprint>/48h/0h/1h/2h]<xpub>))

To Passport/Blockstream Jade/Keystone (multi-sig) (UR:Bytes format encoding):

# <Wallet Name> Multisig setup file (created by Sparrow)
#
Name: pythcoiner
Policy: 2 of 2
Derivation: m/48'/0'/1'/2'
Format: P2WSH

<fingerprint>: <xpub>
<fingerprint>: <xpub>

To Blue Wallet (multi-sig) (single QRCode):

# Blue Wallet Multisig setup file (created by Sparrow)
#
Name: pythcoiner
Policy: 2 of 2
Derivation: m/48'/0'/1'/2'
Format: P2WSH

<fingerprint>: <xpub>
<fingerprint>: <xpub>

To BSMS (multi-sig) (UR:Bytes format encoding)

BSMS 1.0
wsh(sortedmulti(2,[<fingerprint>/48h/0h/1h/2h]<xpub>,[<fingerprint>/48h/0h/1h/2h]<xpub>))
/0/*,/1/*
<bc1_adress>

@pythcoiner
Copy link
Collaborator Author

pythcoiner commented Jun 19, 2023

The label field usage is not clear for me. Can it be removed or is it part of a communication standard ?
If i understand correctly descriptor_alias would be equivalent of the wallet name required by a Ledger device, so as a open question: can we rename it for wallet_name or is it bad naming ?

As the descriptor shows in my last post, I think it's cleaner to keep only the label field for the wallet name (Specter/SeedSigner) and keep Name field for other ones.

@pythcoiner
Copy link
Collaborator Author

keys_aliases seems to be a good improvement for descriptor readability but we need to be careful as device cannot trust laptop and user must verify external xpubs.

Yes, that's why I suggest supplying key aliases as a separate table.
In my seedsigner implementation, I not yet handle them (actually, in the registration process, I first display a letter alias (A, B, C, etc) with the xpub, then after displaying the descriptor with xpubs replaced by alias).
You can have a look here.

@pythcoiner
Copy link
Collaborator Author

  • for descriptor registration, If I understand correctly your proposal: User will first see the template of the descriptor with the keys_aliases and then verify the dict of alias:key with hopefully the their/our labeling like ledger is doing

I actually display first the alias:xpub pairs, then the 'simplified' descriptor (alias instead xpubs).
About their/our labeling, I do not yet notice that on the Ledger, but obviously it's a good point.

Requirement for this labeling would be that Seedsigner loaded the seed before registering the descriptor, which is maybe obvious because Seedsigner needs to provide a PoR by signing the data with a key.

Right, it's the way I actually follow, the key is needed to check that we have 'control' on at least one xpub prior to building the PoR.
About the PoR, what I actually do is first concatenate (mnemonic seed + descriptor + alias) and then hash160(), as @darosior told me (if I understand well), Ledger also doing some king hashing instead of signing (i guess it's faster with Nano limited resources), but I not yet take time to ask @bigspider as I should have.

@pythcoiner
Copy link
Collaborator Author

  • for signing process, user just want change outputs to be labelled with the descriptor alias, do we need to display the descriptor content or is it just a nice to have ? My first opinion is to have less information required with the PoR and the descriptor_alias and descriptor are enough.

Actually, if a valid PoR is supplied in QRCode, I just notice the descriptor alias to the user for a check, then label my own spent inputs as 'from me' and outputs as 'to myself'.

@bigspider
Copy link

Ledger's PoR looks like this:

k           = SLIP21Key(m/"LEDGER-Wallet policy")
policy_id   = SHA256(wallet_policy.serialize())
policy_hmac = HMAC-SHA256(key = k, message = policy_id)

Note that the serialization (and therefore the policy_id) also commits to the name of the policy that is shown to the user when they register, not just the descriptor; that's important as it allows to distinguish different policies without the user having to check the descriptor after the registration (as long as they don't register multiple policies with the same name).
HMAC is a lot faster than signing, and it's recommended in this case − no advantage in asymmetric cryptography since the hardware device is both signer and verifier.

There might be some other relevant info about the approach in the BIP proposal: https://github.com/bitcoin/bips/blob/bb98f8017a883262e03127ab718514abf4a5e5f9/bip-wallet-policies.mediawiki − although it doesn't quite focus on the registration part.

@bigspider
Copy link

Right, it's the way I actually follow, the key is needed to check that we have 'control' on at least one xpub prior to building the PoR. About the PoR, what I actually do is first concatenate (mnemonic seed + descriptor + alias) and then hash160(), as @darosior told me (if I understand well), Ledger also doing some king hashing instead of signing (i guess it's faster with Nano limited resources), but I not yet take time to ask @bigspider as I should have.

En passant: careful with concatenate-and-hash approaches: if you concatenate fields with variable length, it is in general vulnerable to collision attacks. That's solved by length-prefixing anything that is variable length, e.g. for the above: <seed (fixed length)> <length(descriptor)> <descriptor> <length(alias)> <alias> is guaranteed to not have collisions unless the hash function is broken, while <seed (fixed length)> <descriptor> <alias> has collisions (probably hard to exploit in this case, but you never know).

@pythcoiner
Copy link
Collaborator Author

Note that the serialization (and therefore the policy_id) also commits to the name of the policy that is shown to the user when they register, not just the descriptor; that's important as it allows to distinguish different policies without the user having to check the descriptor after the registration (as long as they don't register multiple policies with the same name).

Thanks for your quick answer, I already commit to the name (alias) of policy and also thinking about committing to the alias of each key.

@pythcoiner
Copy link
Collaborator Author

HMAC is a lot faster than signing, and it's recommended in this case − no advantage in asymmetric cryptography since the hardware device is both signer and verifier.

I need to look more into HMAC (I'm not a math nor crypto guy 😅)

There might be some other relevant info about the approach in the BIP proposal: https://github.com/bitcoin/bips/blob/bb98f8017a883262e03127ab718514abf4a5e5f9/bip-wallet-policies.mediawiki − although it doesn't quite focus on the registration part.

Will read it one (many) more time!

En passant: careful with concatenate-and-hash approaches: if you concatenate fields with variable length, it is in general vulnerable to collision attacks. That's solved by length-prefixing anything that is variable length, e.g. for the above: <seed (fixed length)> <length(descriptor)> <descriptor> <length(alias)> <alias> is guaranteed to not have collisions unless the hash function is broken, while <seed (fixed length)> <descriptor> <alias> has collisions (probably hard to exploit in this case, but you never know).

Will care about that, thanks to be in our world Salvatore!

@pythcoiner
Copy link
Collaborator Author

pythcoiner commented Jun 28, 2023

I start working on a rust implementation of an encoder/decoder for QRCode data here (only specter format for now, the next step will be to handle UR format), maybe I should turn it into a WIP PR? @darosior @edouardparis
Btw, it's the first time I have implemented something w/ Rust, so feel free to kick my ass if I do some bullshit! 😂

@pythcoiner
Copy link
Collaborator Author

Here are some notes about UR encoding:
Trying export PSBT from sparrow I got 5 UR encoded chunks:

UR:CRYPTO-PSBT/1-5/LPADAHCFADIDCYCHJSBZZEHDFLHKADHEJOJKIDJYZMADAEGMAOAEAEAEADFYDRDLMUFNGLDWMHFZUYDACFTKEYEODLPFFMROLRREFZAXDKRDCTZTJOIAJTMTWDADAEAEAEAEZCZMZMZMADDICTADAEAEAEAEAECMAEBBYTLKMDMUDTSN
UR:CRYPTO-PSBT/2-5/LPAOAHCFADIDCYCHJSBZZEHDFLJSAXRTNDENJNMUJZNSFEHTMDYLWMCAJLDEFWAEAEAEAEGWADAAECLTTKAXETPSWMRFLAAEAEAXEECEKNGLRHFRHLWMMOEEUEHLFGIEYAKNSGFRKNMYSWASMOIYFLGOGRMWTBWYKTZOAOBZGRBTFZFR
UR:CRYPTO-PSBT/3-5/LPAXAHCFADIDCYCHJSBZZEHDFLUEBELAGLBBEMKBNYWFATVTLRAHUEWPWNTPBEHSWFNBKBMTADLYUTBDVEYKJTIHBEONSWRLJTGHAEAELAADAEAELAAXAEAELAAEADADCTPFDIADAEAEAEAEAECMAEBBIDZOCLIOLGROJODKOECWROSN
UR:CRYPTO-PSBT/4-5/LPAAAHCFADIDCYCHJSBZZEHDFLKOROHPRDOETNPSDIKTWDCESAADAXAAADAEAEAECPAMAOVEOEWKSSMDUEBGMTAOPFUYUTHERECABZDATYKPIDLOHHPKCMKGJNLFETVYYLTDKGCSONSWRLJTGHAEAELAADAEAELAAXAEAELAVWDRVTVY
UR:CRYPTO-PSBT/5-5/LPAHAHCFADIDCYCHJSBZZEHDFLAEAEAEAEAEAEAEAEAECPAOAOYNKKHGDEYLKIGUINREJSKINNHPBWUEAXCHHKATDSGDMYFXJOJZADKOHKVDISUOMKCSONSWRLJTGHAEAELAADAEAELAAXAEAELAAEAEAEAEADAEAEAEAEAELFNNKTWT

which might be combined to turn into a PSBT:

cHNidP8BAFICAAAAAUQqL5M8TiyQQNslGc8yMy+wPriEtUADJLof/HBjbpbqAQAAAAD9////AScfAQAAAAAAFgAU+YxxA8CbNm2TbJxFWpX36x1vKEIAAAAATwEENYfPAzis67yAAAADNBx6Trk7XeuSNN5dRmT4eso7eo/GCZJmR1VLlNbud/sCFd4QgE4UN36a8wfghAXe7PHYEGHzoH6WAYHdC+T1bmUQpca3blQAAIABAACAAwAAgAABAR+wJwEAAAAAABYAFGL7IWeNuHAkdrhbuqLarCd36hzCAQMEAQAAACIGAuSi9MSV3hKWArDb3V+1HRUl1HViiFyqFnttgjjh99J7GKXGt25UAACAAQAAgAMAAIAAAAAAAAAAAAAiAgL2eVco931TabVxfZ5bE94DF1kHJlCPQ3BsAXZZ52jcmBilxrduVAAAgAEAAIADAACAAAAAAAEAAAAAcHNidP8BAFICAAAAAUQqL5M8TiyQQNslGc8yMy+wPriEtUADJLof/HBjbpbqAQAAAAD9////AScfAQAAAAAAFgAU+YxxA8CbNm2TbJxFWpX36x1vKEIAAAAATwEENYfPAzis67yAAAADNBx6Trk7XeuSNN5dRmT4eso7eo/GCZJmR1VLlNbud/sCFd4QgE4UN36a8wfghAXe7PHYEGHzoH6WAYHdC+T1bmUQpca3blQAAIABAACAAwAAgAABAR+wJwEAAAAAABYAFGL7IWeNuHAkdrhbuqLarCd36hzCAQMEAQAAACIGAuSi9MSV3hKWArDb3V+1HRUl1HViiFyqFnttgjjh99J7GKXGt25UAACAAQAAgAMAAIAAAAAAAAAAAAAiAgL2eVco931TabVxfZ5bE94DF1kHJlCPQ3BsAXZZ52jcmBilxrduVAAAgAEAAIADAACAAAAAAAEAAAAA

@edouardparis
Copy link
Member

Thanks for the information, very interesting! I will try to have a look before the end of the week

@seedhammer
Copy link

We are very interested in standardizing the format and serialization of wallet setups. Because we're engraving descriptors on steel plates, we're particularly interested in compact encodings.

Some notes:

  • As described earlier in this thread, there are several competing formats for wallet export/import with varying features.
  • The Blockchain Commons' standard, BCR-2020-010 is well supported and compact but recently deprecated.
  • The replacement enveloped format is by its recentness not supported (anywhere?). Also, the envelope format is rather complex (CBOR) and doesn't buy us much. In particular, the output descriptor itself is encoded in the usual textual form.
  • The wallet policies BIP is incomplete in the sense that it doesn't specify a serialization format. It does specify a way to refer to keys by index (@1..n), however in a way that seems incompatible with Miniscript (it uses text names for keys, and @ for likelyhoods)

To our mind, a (new) standard should:

  • Have widespread support by the ecosystem
  • Be standardized through the Bitcoin BIP process

Ideally, also

  • Be simple and compact, e.g. by specifying an encoding similar to PSBT.
  • Be future compatible: support miniscript as well as future script languages (e.g. Simplicity).

Given a serialization, the question of QR encoding is a separate concern. Choosing the Commons UR format is a straightforward choice, because it's already in widespread use.

Blockchain Commons UR Types format is used by almost all air-gapped signing devices (including Blockstream Jade and SeedSigner), this encoding looks quite more complex (I don't dig deep into this format implementations, but it use CBOR-encoded data structures ), and I don't know whereas there is some Rust implementation of it (SeedSigner used the Foundation and selfcustody implementations together

Indeed, the UR format is quite complex. However, you can avoid a CBOR encoder by encoding the payload as a CBOR binary blob which is just a byte, a length and the payload itself. The fountain encoding part of UR is another complexity; fortunately it's optional and can be ignored (BlueWallet does this).

@shannona
Copy link

Hi @pythcoiner. Even if you're not comfortable with CBOR, the UR structures should be quite usable, because they should hide those CBOR details behind the tagged, interoperable UR format.

We have a comprehensive set of documents on UR here, and admittedly it leans hard on how the UR converts to CBOR, so maybe we need something that talks about just the UR format?

https://developer.blockchaincommons.com/ur/

(Let me know if there's something you think is missing from the docs that would make them more approachable.)

We do have a new set of Rust libraries that are in Community Review, which means we're currently taking feedback on not just how they work, but also whether their API is what you expect, and whether they're doing the job you need.

The UR library is here:
https://github.com/BlockchainCommons/bc-ur-rust

The rest of the stack, and a place for any feedback, is here:
BlockchainCommons/Gordian-Developer-Community#116

@FoundationKen
Copy link

Hi all! I just want to chime in here with some thoughts. IMO, the BC envelope format is pretty simple in its construction and enables a lot of powerful use cases (that go well beyond descriptors too). This video gives a great overview of some of the possibilities:

https://www.youtube.com/watch?v=OcnpYqHn8NQ

Although the BC envelope standard is not yet widespread, the same would be true of any other new standard we might define. Working with a group like Blockchain Commons to document and manage whatever standard we arrive at is pretty valuable, IMO.

The "complexity" of CBOR is mostly hidden behind existing CBOR libraries, but having implemented a subset of CBOR myself previously, the code is pretty straightforward. Implementations are available for all popular languages, though, so nobody needs to write this code, just the code that builds the messages on top of CBOR:

https://cbor.io/impls.html

The other question raised above is whether descriptors should be encoded as strings or as CBOR or in some other binary format. I'm unsure what the motivation was to deprecate the BC CBOR encoding above, but having a deconstructed binary format does seem valuable vs. a string that has to be parsed.

@seedhammer
Copy link

seedhammer commented Oct 14, 2023

We've posted a sketch BIP: BlockchainCommons/Research#135.

@seedhammer
Copy link

For everyone interested in joining the discussion, we'll be at the next Gordian Developer community meeting Nov. 1. to present the BIP sketch.

@seedhammer
Copy link

Our BIP proposal is here: https://github.com/seedhammer/bips/blob/master/bip-psbt-descriptors.mediawiki. I'm looking into proof of registration and whether it can be added to the proposal or in another.

@seedhammer
Copy link

Ledger's PoR looks like this:

k           = SLIP21Key(m/"LEDGER-Wallet policy")
policy_id   = SHA256(wallet_policy.serialize())
policy_hmac = HMAC-SHA256(key = k, message = policy_id)

Note that the serialization (and therefore the policy_id) also commits to the name of the policy that is shown to the user when they register, not just the descriptor; that's important as it allows to distinguish different policies without the user having to check the descriptor after the registration (as long as they don't register multiple policies with the same name). HMAC is a lot faster than signing, and it's recommended in this case − no advantage in asymmetric cryptography since the hardware device is both signer and verifier.

For standardization of proof of registrations, I'm not convinced a symmetric HMAC is the best choice. Some advantages of actual, asymmetric signatures:

  • Similar implementation complexity, because the wallet software already includes signing logic.
  • The coordinator software can ensure that the descriptor has been authorized and not been tampered with.
  • The user can transfer the descriptor to other (watch-only or coordinator) devices without worrying about tampering.
  • With MuSig2 (I believe) it's possible to construct a constant sized proof registration that includes all co-signers.

@pythcoiner
Copy link
Collaborator Author

For standardization of proof of registrations, I'm not convinced a symmetric HMAC is the best choice. Some advantages of actual, asymmetric signatures:

i think the PoR do not have to be standardized, but the PoR should be an optional field that the software wallet/coordinator can use.

  • The coordinator software can ensure that the descriptor has been authorized and not been tampered with.
  • The user can transfer the descriptor to other (watch-only or coordinator) devices without worrying about tampering.

to me the only purpose of the PoR field in our context is to allow the signer to 'outsource' the storage of the PoR in the way to stay stateless. nothing else than the signer have to check the PoR is genuine I guess. If the PoR is valid the signer should only not ask user to review the descriptor.

  • With MuSig2 (I believe) it's possible to construct a constant sized proof registration that includes all co-signers.

i don't see any reason to doing that as each PoR is the proof the descriptor have been reviewed for a specific private key context, but maybe I missing something?

@pythcoiner
Copy link
Collaborator Author

btw i'm interested to have @bigspider thought about this BIP proposal

@bigspider
Copy link

  • Similar implementation complexity, because the wallet software already includes signing logic.

HMACs (and therefore SLIP-21) are quite easy to implement, much easier than signatures and BIP32 derivations. The running time for BIP32 derivation + signature verification on embedded hardware can be noticeable (tenths of a second), while HMACs ans SLIP-21 are instant even on the weakest hardware. Also, HMAC-256 is only 32 bytes.

  • The coordinator software can ensure that the descriptor has been authorized and not been tampered with.
  • The user can transfer the descriptor to other (watch-only or coordinator) devices without worrying about tampering.

Both are true for HMACs, too. More generally, when the signer is also the verifier, there is no difference in cryptographic properties between HMACs and signatures.

  • With MuSig2 (I believe) it's possible to construct a constant sized proof registration that includes all co-signers.

Even ignoring the implementation complexity, that would force some non-trivial cooperation (and two rounds of communication) among all cosigners for an action that seems inherently an individual action to me (that is: registering a new wallet on a signing device).

@bigspider
Copy link

i think the PoR do not have to be standardized, but the PoR should be an optional field that the software wallet/coordinator can use.

Some people (e.g. at Unchained Capital, who integrated with them in the context of multisig) did show interest in standardizing the PoR, in order to enable portability across vendors. I think it would be nice in theory, but agreeing on a standard has quite high coordination cost and I'm not sure the benefits in this situation are substantial.

Also, I'm not sure everyone would agree with the approach I chose (fully stateless PoR derived from the seed, unrevocable) versus other approaches, for example:

  • PoR based on a key stored on the device; not fully stateless, but all PoR can be invalidated if desired
  • just store individual registered policies on the device (can revoke individual registration)

I don't think revocation is very useful in this context, but surely some people will disagree.

Note that PoR has no use in software wallets - rather, they just have to store it for the hardware signer precisely to protect them from the software wallet.

to me the only purpose of the PoR field in our context is to allow the signer to 'outsource' the storage of the PoR in the way to stay stateless. nothing else than the signer have to check the PoR is genuine I guess. If the PoR is valid the signer should only not ask user to review the descriptor.

+1

btw i'm interested to have @bigspider thought about this BIP proposal

There is definitely some shared grounds between this proposal and the work I've been doing and the approach from wallet policies:

  • I also tried to minimize the wallet policy size (although for me the goal was visual inspection from the user, rather than transmission over bandwidth-constrained channels like QR codes)
  • Splitting apart the "descriptor template" from the xpubs is very useful
  • Adding descriptors/wallet policies to PSBT seems necessary to me, as signing devices need that knowledge at signing time (at the mininum, in order to recognize change addresses).

However, I'm wary of adding to the PSBT other purposes other than whatever is needed in signing flows. The design space of all the possible signing flows is already very large and mostly not yet standardized.

A standard to add descriptors/wallet policies to PSBTs should imho:

  • allow multiple descriptors in the global section
  • specify which descriptor is used for each input/output (otherwise, it forces the assumption that each input or change output is using the same policy, which is not necessarily true)

Unfortunately, any way of adding descriptors to the PSBT inevitably adds redundancy with the existing PSBT fields, as information is going to be duplicated (e.g. it's present among the PSBT fields, but it could also be deduced from the descriptors); that clashes with the desire of minimizing the PSBTs, which seems crucial for QR-based signing flows.

I don't think this redundancy can be avoided without a new PSBT version.

@pythcoiner
Copy link
Collaborator Author

pythcoiner commented Jan 5, 2024

Some people (e.g. at Unchained Capital, who integrated with them in the context of multisig) did show interest in standardizing the PoR, in order to enable portability across vendors. I think it would be nice in theory, but agreeing on a standard has quite high coordination cost and I'm not sure the benefits in this situation are substantial.

Also, I'm not sure everyone would agree with the approach I chose (fully stateless PoR derived from the seed, unrevocable) versus other approaches, for example:

  • PoR based on a key stored on the device; not fully stateless, but all PoR can be invalidated if desired
  • just store individual registered policies on the device (can revoke individual registration)

I mean I don't think the PoR should be standardized in the BIP proposal from @seedhammer IMHO, to be standardized somewhere else I don't have a strong opinion.

@seedhammer
Copy link

  • Similar implementation complexity, because the wallet software already includes signing logic.

HMACs (and therefore SLIP-21) are quite easy to implement, much easier than signatures and BIP32 derivations. The running time for BIP32 derivation + signature verification on embedded hardware can be noticeable (tenths of a second), while HMACs ans SLIP-21 are instant even on the weakest hardware. Also, HMAC-256 is only 32 bytes.

  • The coordinator software can ensure that the descriptor has been authorized and not been tampered with.
  • The user can transfer the descriptor to other (watch-only or coordinator) devices without worrying about tampering.

Both are true for HMACs, too. More generally, when the signer is also the verifier, there is no difference in cryptographic properties between HMACs and signatures.

How can a watch-only or coordinator without seeds derive the symmetric SLIP21 keys?

  • With MuSig2 (I believe) it's possible to construct a constant sized proof registration that includes all co-signers.

Even ignoring the implementation complexity, that would force some non-trivial cooperation (and two rounds of communication) among all cosigners for an action that seems inherently an individual action to me (that is: registering a new wallet on a signing device).

I'm not sure whether it's true in practice, but the idea is that the complexity in implementation and communication rounds is paid during the initial setup of the descriptor among signers anyway.

i don't see any reason to doing that as each PoR is the proof the descriptor have been reviewed for a specific private key context, but maybe I missing something?

AFAIK, a MuSig(2) (or FROST) descriptor can be made constant size regardless of number of co-signers. It would be nice that the PoR be constant size as well.

i think the PoR do not have to be standardized, but the PoR should be an optional field that the software wallet/coordinator can use.

That would be unfortunate, in that we'll lose interoperability among different signer brands. If that's the intent, why not use the PSBT_GLOBAL_PROPRIETARY field?

@pythcoiner
Copy link
Collaborator Author

That would be unfortunate, in that we'll lose interoperability among different signer brands. If that's the intent, why not use the PSBT_GLOBAL_PROPRIETARY field?

my mean your BIP proposal target the communication level but should not target to care about how is process the PoR.

as mentioned ealier, in my mind, the PoR is just a way to keep signer stateless but signer can recognize that the descriptor have already been reviewed (and assigned an alias?) by user

btw i'm also interested to have though from @benma, @scgbckbone about PoR, do you have such thing in your miniscript signing workflow?

@stepansnigirev, i think the specter DIY is not stateless about the PoR thing, but i'm also interested about you though.

@FoundationKen i think miniscript signing is on your roadmap, so interested also by your though.

@bigspider
Copy link

How can a watch-only or coordinator without seeds derive the symmetric SLIP21 keys?

The PoR is both created and verified by the signing devices, as both actions require access to the private keys. The software wallets / coordinators only store the PoR.

@seedhammer
Copy link

That would be unfortunate, in that we'll lose interoperability among different signer brands. If that's the intent, why not use the PSBT_GLOBAL_PROPRIETARY field?

my mean your BIP proposal target the communication level but should not target to care about how is process the PoR.

The BIP should care about format because of interoperability. If I switch hardware signing device models I shouldn't have to redo the PoR.

How can a watch-only or coordinator without seeds derive the symmetric SLIP21 keys?

The PoR is both created and verified by the signing devices, as both actions require access to the private keys. The software wallets / coordinators only store the PoR.

Being able to tell the user from the wallet/coordinator UI that a particular descriptor is signed seems like a useful feature to me.

@pythcoiner
Copy link
Collaborator Author

If I switch hardware signing device models I shouldn't have to redo the PoR.

I think we should have to redo PoR if using key on different HW as some vendor can (maybe) commit to the signer unique id in their PoR calculation process (remember PoR is just prrof that user have rewieved the descriptor)

@pythcoiner
Copy link
Collaborator Author

Being able to tell the user from the wallet/coordinator UI that a particular descriptor is signed seems like a useful feature to me.

I'm interested by the usecase you had in mind?

@seedhammer
Copy link

If I switch hardware signing device models I shouldn't have to redo the PoR.

I think we should have to redo PoR if using key on different HW as some vendor can (maybe) commit to the signer unique id in their PoR calculation process (remember PoR is just prrof that user have rewieved the descriptor)

Good point, but whatever extra data to commit to can be added to the PSBT key data, no?

Being able to tell the user from the wallet/coordinator UI that a particular descriptor is signed seems like a useful feature to me.

I'm interested by the usecase you had in mind?

I would like to verify the integrity of the descriptor when generating receive addresses.

@pythcoiner
Copy link
Collaborator Author

Good point, but whatever extra data to commit to can be added to the PSBT key data, no?

what you mean by 'added to the PSBT key'?

I would like to verify the integrity of the descriptor when generating receive addresses.

in my mind we using Hardware Signers because we assume that our software wallet/ coordinator can be corrupted, if so the corrupted software can display the descriptor as genuine even if not, whatever proof you have.... so user should only trust an adress verified on the Hardware Signer, its less convenient but the only safe way i guess.

here an interesting post from @kloaec around this topic

@seedhammer
Copy link

Good point, but whatever extra data to commit to can be added to the PSBT key data, no?

what you mean by 'added to the PSBT key'?

A PSBT map entry contains two values, the key data and the value data. The extra commitment data could be stored in the key data, and the signature itself in the value data.

I would like to verify the integrity of the descriptor when generating receive addresses.

in my mind we using Hardware Signers because we assume that our software wallet/ coordinator can be corrupted, if so the corrupted software can display the descriptor as genuine even if not, whatever proof you have.... so user should only trust an adress verified on the Hardware Signer, its less convenient but the only safe way i guess.

Using a signer for verifying addresses is a secure way to generate addresses, but has two important downsides: (1) less convenience and (2) temptation to leave the signer in a more exposed location thus increasing the risk of theft.

Consider the cold storage wallet that receives much more often than it spends. I argue its safer to generate addresses by having your signed descriptor on, say, a laptop and a phone and always pairwise compare the generated addresses before using them. The descriptor signature ensures that an attacker cannot tamper with the descriptor in transit.

@seedhammer
Copy link

With respect to the complexity of an asymmetric signature, I suppose we can re-use https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants