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:
Alice
contractsBob
as her co-signer.Bob
givesAlice
his public key shareX2
he generated specifically forAlice
Alice
generates the shared public keyX
and the corresponding taproot addresst
Alice
keepsX
private and sends funds tot
Alice
creates transaction imagem
, and generatesb
random scalarAlice
initiates a secure session withBob
and authenticates herself with the chosen 2FA methodBob
revealesAlice
his single use nonce pointK2
Alice
computesR = K1 + K2 + bX
, computese = hash(R||X||m)
, then blindse' = e + b
Alice
calculates signature partk1 + e'*x1
and sendse'
toBob
Bob
responds with signature partk2 + e'*x2
toAlice
Alice
aggregates the signature parts tos = (k1 + e'*x1) + (k2 + e'*x2)
signatureAlice
broadcasts 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(
4
years), 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?