Co-signers can provide economy of scale for both cyber and physical security beyond the means of the ordinary users, however traditional multisig comes at a heavy price regarding privacy. A co-signer would learn about the user's bitcoin holdings and all transactions. The user could blind a message, send it to the co-signer, authenticate via 2FA, receive blinded sig, unblind, then aggregate with the users signature piece, and calculate a signature the co-signer can't recognize, but satisfies the 2-of-2 shared public key. The co-signer would not know the public key of the user, nor would be able to recognize any signatures on-chain.
X = X1 + X2
K1 = k1·G
K2 = k2·G
R = K1 + K2 + b·X
e = hash(R||X||m)
e' = e + b
s = (k1 + e'·x1) + (k2 + e'·x2)
s = (k1 + k2 + b(x1 + x2)) + e(x1 + x2)
sG = (K1 + K2 + b·X) + e·X
sG = R + e·X
Rv = s·G - e·X
ev = hash(R||X||m)
e ?= ev
Alice being the user and Bob the blind co-signer:
AlicecontractsBobas her co-signer.BobgivesAlicehis public key shareX2he generated specifically forAliceAlicegenerates the shared public keyXand the corresponding taproot addresstAlicekeepsXprivate and sends funds tot
Alicecreates transaction imagem, and generatesbrandom scalarAliceinitiates a secure session withBoband authenticates herself with the chosen 2FA methodBobrevealesAlicehis single use nonce pointK2AlicecomputesR = K1 + K2 + bX, computese = hash(R||X||m), then blindse' = e + bAlicecalculates signature partk1 + e'*x1and sendse'toBobBobresponds with signature partk2 + e'*x2toAliceAliceaggregates the signature parts tos = (k1 + e'*x1) + (k2 + e'*x2)signatureAlicebroadcasts her signed transaction
Bob never learns X or R or e or s so he can't identify the broadcasted transaction.
Blind Schnorr signatures could be easily attacked in a general setting, but in case of 2FA co-signers that do not reuse public keys between customers this is not an issue. Signing is tied to identity, rate limiting signing is trivial and even inherent to the multi-factor authentication. The user has no reasonable motivation to attack the co-signer but even for users it would be unfeasible to run multiple signing requests per second. A third party attacker is expected to fail the 2FA authentication.
Schnorr threshold key and signature aggregation may also be possible in a blinded way, in the meantime it can trivially be emulated:
- Keypath:
- pk(
A+B)
- pk(
- Scriptpath or(...)
- pk(
B+C1) - pk(
B+C2+C3) - and( p(
A), thresh(2, pk(C1), pk(C2), pk(C3)) ) - and( older(
4years), tresh(2, pk(A), pk(B), pk(C1), pk(C2), pk(C3)) )
- pk(
With public keys (or rather xpubs) A for Alice, B for Bob and spare keys C1, C2, C3. Alice could for example hide C1, put C2 in a safe deposite box or embed it in her will, and hand out C3 to each family member or inheritor. Normally Alice would spend in a single sig keyspend transaction that Bob co-signs. In case Bob stops cooperating, Alice can recover using her master key and any 2 of the 3 spare keys. Alice has some blind signed options with Bob as co-signer and spare keys to recover if she loses access to her master key A. Finally after 4 years Alice or her inheritors could recover with any 2 of the 5 keys.
Redundant descriptor backups that help retain privacy, using BIP-32/39/44 with the purpose 5719106 ("WDB" is 0x574442 bigendian). For example 2 of 5 Shamir's secret sharing pieces may be required to reconstruct the recovery phrase. This recovery phrase is a seed to generate the encrypted backups. Having the recovery phrase and any of the encrypted wallet descriptor backup files created with it, is enough to recover the wallet descriptor. With that, the addresses used and the onchain balances and transactions are identifiable. The encrypted backups can be safely sent over email or uploaded to cloud. Even if their filename is changed or truncated the index allows for generating the corresponting private_key. Example derivation path: m/5719106'/0'/0'/0'/1' for the second backup file.
| Value | Description |
|---|---|
| recovery_phrase | 128 bit entropy / 12 bip-39 words with nonstandard checksum |
| s1..s5 | 2/5 shamir's secret sharing pieces of the recovery phrase |
| wallet_descriptor | {xpubs A, B, C1, C2, C3, parameters, taproot address construction template} |
| derivation_path | f"m/5719106'/{coin}'/{account}'/0'/{index}'" |
| private_key | dk(recovery_phrase, derivation_path) |
| backup_id | ripemd160(sha256(pubkey(private_key))) |
| wallet_descriptor_backup | index||aes(wallet_descriptor, private_key) |
| filename | f"{backup_id}.bak" |
Alternative Blind Schnorr?