mirror of
https://github.com/muun/recovery.git
synced 2025-11-12 23:01:40 -05:00
Update project structure and build process
This commit is contained in:
592
libwallet/musig/musig.go
Normal file
592
libwallet/musig/musig.go
Normal file
@@ -0,0 +1,592 @@
|
||||
package musig
|
||||
|
||||
// #include <stdlib.h>
|
||||
// #include "umbrella.h"
|
||||
// #cgo CFLAGS: -DECMULT_WINDOW_SIZE=15 -DECMULT_GEN_PREC_BITS=4 -DSECP256K1_BUILD
|
||||
import "C"
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/muun/libwallet/btcsuitew/chainhashw"
|
||||
)
|
||||
|
||||
func toUchar(buf []byte) *C.uchar {
|
||||
// See https://stackoverflow.com/a/51428826 on why this is needed
|
||||
var bufptr *byte
|
||||
if cap(buf) > 0 {
|
||||
bufptr = &(buf[:1][0])
|
||||
}
|
||||
return (*C.uchar)(bufptr)
|
||||
}
|
||||
|
||||
var ctx *C.struct_secp256k1_context_struct
|
||||
|
||||
func init() {
|
||||
ctx = C.secp256k1_context_create(C.SECP256K1_CONTEXT_SIGN | C.SECP256K1_CONTEXT_VERIFY)
|
||||
// TODO: consider using secp256k1_context_set_illegal_callback
|
||||
}
|
||||
|
||||
func CombinePubKeysWithTweak(userKey, muunKey *btcec.PublicKey, customTweak []byte) (*btcec.PublicKey, error) {
|
||||
combined, err := combinePubKeys(userKey, muunKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tweak, err := tagTweakOrDefault(combined, customTweak)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var tweakPubKey C.secp256k1_pubkey
|
||||
if C.secp256k1_xonly_pubkey_tweak_add(
|
||||
ctx,
|
||||
&tweakPubKey,
|
||||
combined,
|
||||
toUchar(tweak[:]),
|
||||
) == 0 {
|
||||
return nil, fmt.Errorf("failed to tweak key")
|
||||
}
|
||||
|
||||
var serialized [33]byte
|
||||
var serializedSize C.size_t
|
||||
serializedSize = 33
|
||||
if C.secp256k1_ec_pubkey_serialize(
|
||||
ctx,
|
||||
toUchar(serialized[:]),
|
||||
&serializedSize,
|
||||
&tweakPubKey,
|
||||
C.SECP256K1_EC_COMPRESSED,
|
||||
) == 0 {
|
||||
return nil, fmt.Errorf("failed to serialize tweaked key")
|
||||
}
|
||||
|
||||
return btcec.ParsePubKey(serialized[:], btcec.S256())
|
||||
}
|
||||
|
||||
func combinePubKeys(userKey *btcec.PublicKey, muunKey *btcec.PublicKey) (*C.secp256k1_xonly_pubkey, error) {
|
||||
|
||||
// Safe C-interop rules require C pointer (ie the array) can't contain go
|
||||
// pointers. These go into an array, so we need to allocate manually.
|
||||
userXOnly := (*C.secp256k1_xonly_pubkey)(C.malloc(C.sizeof_secp256k1_xonly_pubkey))
|
||||
defer C.free(unsafe.Pointer(userXOnly))
|
||||
|
||||
muunXOnly := (*C.secp256k1_xonly_pubkey)(C.malloc(C.sizeof_secp256k1_xonly_pubkey))
|
||||
defer C.free(unsafe.Pointer(muunXOnly))
|
||||
|
||||
if C.secp256k1_xonly_pubkey_parse(ctx, userXOnly, toUchar(userKey.SerializeCompressed()[1:])) == 0 {
|
||||
return nil, fmt.Errorf("failed to parse user key")
|
||||
}
|
||||
|
||||
if C.secp256k1_xonly_pubkey_parse(ctx, muunXOnly, toUchar(muunKey.SerializeCompressed()[1:])) == 0 {
|
||||
return nil, fmt.Errorf("failed to parse muun key")
|
||||
}
|
||||
|
||||
keys := []*C.secp256k1_xonly_pubkey{
|
||||
userXOnly,
|
||||
muunXOnly,
|
||||
}
|
||||
|
||||
var combined C.secp256k1_xonly_pubkey
|
||||
if C.secp256k1_musig_pubkey_agg(
|
||||
ctx,
|
||||
nil,
|
||||
&combined,
|
||||
nil,
|
||||
(**C.secp256k1_xonly_pubkey)(&keys[:1][0]),
|
||||
2,
|
||||
) == 0 {
|
||||
return nil, fmt.Errorf("failed to combne keys")
|
||||
}
|
||||
|
||||
return &combined, nil
|
||||
}
|
||||
|
||||
// RandomSessionId returns a safe random session id. Session IDs must not be
|
||||
// repeated otherwise private keys are compromised.
|
||||
func RandomSessionId() [32]byte {
|
||||
var buf [32]byte
|
||||
_, err := rand.Read(buf[:])
|
||||
if err != nil {
|
||||
panic("couldn't read random bytes")
|
||||
}
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
// GeneratePubNonce returns the pub nonce for a given session id
|
||||
func GeneratePubNonce(sessionId [32]byte) [66]byte {
|
||||
|
||||
var secnonce C.secp256k1_musig_secnonce
|
||||
var pubNonce C.secp256k1_musig_pubnonce
|
||||
|
||||
res := C.secp256k1_musig_nonce_gen(
|
||||
ctx,
|
||||
&secnonce,
|
||||
&pubNonce,
|
||||
toUchar(sessionId[:]),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
|
||||
if res == 0 {
|
||||
panic("failed to generate nonce")
|
||||
}
|
||||
|
||||
var pubNonceBytes [66]byte
|
||||
res = C.secp256k1_musig_pubnonce_serialize(
|
||||
ctx,
|
||||
toUchar(pubNonceBytes[:]),
|
||||
&pubNonce,
|
||||
)
|
||||
if res == 0 {
|
||||
panic("failed to serialize pub nonce")
|
||||
}
|
||||
|
||||
return pubNonceBytes
|
||||
}
|
||||
|
||||
// AddUserSignatureAndCombine with partial muun signature.
|
||||
func AddUserSignatureAndCombine(
|
||||
data [32]byte,
|
||||
userKey *btcec.PrivateKey,
|
||||
muunKey *btcec.PublicKey,
|
||||
rawMuunPartialSig [32]byte,
|
||||
rawMuunPubNonce [66]byte,
|
||||
sessionId [32]byte,
|
||||
customTweak []byte,
|
||||
) ([64]byte, error) {
|
||||
|
||||
var signature [64]byte
|
||||
|
||||
// Safe C-interop rules require C pointer (ie the array) can't contain go
|
||||
// pointers. These go into an array, so we need to allocate manually.
|
||||
userXOnly := (*C.secp256k1_xonly_pubkey)(C.malloc(C.sizeof_secp256k1_xonly_pubkey))
|
||||
defer C.free(unsafe.Pointer(userXOnly))
|
||||
|
||||
muunXOnly := (*C.secp256k1_xonly_pubkey)(C.malloc(C.sizeof_secp256k1_xonly_pubkey))
|
||||
defer C.free(unsafe.Pointer(muunXOnly))
|
||||
|
||||
if C.secp256k1_xonly_pubkey_parse(
|
||||
ctx,
|
||||
userXOnly,
|
||||
toUchar(userKey.PubKey().SerializeCompressed()[1:]),
|
||||
) == 0 {
|
||||
return signature, fmt.Errorf("failed to make xonly from user key")
|
||||
}
|
||||
|
||||
if C.secp256k1_xonly_pubkey_parse(
|
||||
ctx,
|
||||
muunXOnly,
|
||||
toUchar(muunKey.SerializeCompressed()[1:]),
|
||||
) == 0 {
|
||||
return signature, fmt.Errorf("failed to make xonly from user key")
|
||||
}
|
||||
|
||||
keys := []*C.secp256k1_xonly_pubkey{
|
||||
userXOnly,
|
||||
muunXOnly,
|
||||
}
|
||||
|
||||
var combined C.secp256k1_xonly_pubkey
|
||||
var keyaggCache C.secp256k1_musig_keyagg_cache
|
||||
if C.secp256k1_musig_pubkey_agg(
|
||||
ctx,
|
||||
nil,
|
||||
&combined,
|
||||
&keyaggCache,
|
||||
(**C.secp256k1_xonly_pubkey)(&keys[:1][0]),
|
||||
2,
|
||||
) == 0 {
|
||||
return signature, fmt.Errorf("failed to combine keys")
|
||||
}
|
||||
|
||||
var secnonce C.secp256k1_musig_secnonce
|
||||
userPubNonce := (*C.secp256k1_musig_pubnonce)(C.malloc(C.sizeof_secp256k1_musig_pubnonce))
|
||||
defer C.free(unsafe.Pointer(userPubNonce))
|
||||
|
||||
if C.secp256k1_musig_nonce_gen(
|
||||
ctx,
|
||||
&secnonce,
|
||||
userPubNonce,
|
||||
toUchar(sessionId[:]),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
) == 0 {
|
||||
return signature, fmt.Errorf("failed to generate user nonce")
|
||||
}
|
||||
|
||||
muunPubNonce := (*C.secp256k1_musig_pubnonce)(C.malloc(C.sizeof_secp256k1_musig_pubnonce))
|
||||
defer C.free(unsafe.Pointer(muunPubNonce))
|
||||
|
||||
if C.secp256k1_musig_pubnonce_parse(
|
||||
ctx,
|
||||
muunPubNonce,
|
||||
toUchar(rawMuunPubNonce[:]),
|
||||
) == 0 {
|
||||
return signature, fmt.Errorf("failed to parse muun pub nonce")
|
||||
}
|
||||
|
||||
tweak, err := tagTweakOrDefault(&combined, customTweak)
|
||||
if err != nil {
|
||||
return signature, err
|
||||
}
|
||||
|
||||
var tweakedPubKey C.secp256k1_pubkey
|
||||
if C.secp256k1_musig_pubkey_tweak_add(
|
||||
ctx,
|
||||
&tweakedPubKey,
|
||||
toUchar(tweak[:]),
|
||||
&keyaggCache,
|
||||
) == 0 {
|
||||
return signature, fmt.Errorf("failed to tweak key")
|
||||
}
|
||||
|
||||
// The API is kinda unhappy, and now requires us to transform the
|
||||
// tweaked pub key to x-only and overwrite the previous combined key
|
||||
if C.secp256k1_xonly_pubkey_from_pubkey(
|
||||
ctx,
|
||||
&combined,
|
||||
nil,
|
||||
&tweakedPubKey,
|
||||
) == 0 {
|
||||
return signature, fmt.Errorf("failed to transform tweaked key to xonly")
|
||||
}
|
||||
|
||||
var aggNonce C.secp256k1_musig_aggnonce
|
||||
|
||||
pubNonces := []*C.secp256k1_musig_pubnonce{
|
||||
userPubNonce,
|
||||
muunPubNonce,
|
||||
}
|
||||
if C.secp256k1_musig_nonce_agg(
|
||||
ctx,
|
||||
&aggNonce,
|
||||
(**C.secp256k1_musig_pubnonce)(&pubNonces[:1][0]),
|
||||
2,
|
||||
) == 0 {
|
||||
return signature, fmt.Errorf("failed to aggregate nonces")
|
||||
}
|
||||
|
||||
var session C.secp256k1_musig_session
|
||||
|
||||
if C.secp256k1_musig_nonce_process(
|
||||
ctx,
|
||||
&session,
|
||||
&aggNonce,
|
||||
toUchar(data[:]),
|
||||
&keyaggCache,
|
||||
nil,
|
||||
) == 0 {
|
||||
return signature, fmt.Errorf("failed to process nonces")
|
||||
}
|
||||
|
||||
// Heap allocated since it will go in an array soon
|
||||
muunPartialSig := (*C.secp256k1_musig_partial_sig)(
|
||||
C.malloc(C.sizeof_secp256k1_musig_partial_sig),
|
||||
)
|
||||
defer C.free(unsafe.Pointer(muunPartialSig))
|
||||
|
||||
if C.secp256k1_musig_partial_sig_parse(
|
||||
ctx,
|
||||
muunPartialSig,
|
||||
toUchar(rawMuunPartialSig[:]),
|
||||
) == 0 {
|
||||
return signature, fmt.Errorf("failed to parse muun partial sig")
|
||||
}
|
||||
|
||||
if C.secp256k1_musig_partial_sig_verify(
|
||||
ctx,
|
||||
muunPartialSig,
|
||||
pubNonces[1],
|
||||
muunXOnly,
|
||||
&keyaggCache,
|
||||
&session,
|
||||
) == 0 {
|
||||
return signature, fmt.Errorf("partial sig is invalid")
|
||||
}
|
||||
|
||||
var userKeyPair C.secp256k1_keypair
|
||||
if C.secp256k1_keypair_create(
|
||||
ctx,
|
||||
&userKeyPair,
|
||||
toUchar(userKey.Serialize()),
|
||||
) == 0 {
|
||||
return signature, fmt.Errorf("failed to create user key pair")
|
||||
}
|
||||
|
||||
// Heap allocated since it will go in an array soon
|
||||
userPartialSig := (*C.secp256k1_musig_partial_sig)(
|
||||
C.malloc(C.sizeof_secp256k1_musig_partial_sig),
|
||||
)
|
||||
defer C.free(unsafe.Pointer(userPartialSig))
|
||||
|
||||
if C.secp256k1_musig_partial_sign(
|
||||
ctx,
|
||||
userPartialSig,
|
||||
&secnonce,
|
||||
&userKeyPair,
|
||||
&keyaggCache,
|
||||
&session,
|
||||
) == 0 {
|
||||
return signature, fmt.Errorf("failed to sign with user key")
|
||||
}
|
||||
|
||||
partialSigs := []*C.secp256k1_musig_partial_sig{
|
||||
userPartialSig, muunPartialSig,
|
||||
}
|
||||
|
||||
if C.secp256k1_musig_partial_sig_agg(
|
||||
ctx,
|
||||
toUchar(signature[:]),
|
||||
&session,
|
||||
(**C.secp256k1_musig_partial_sig)(&partialSigs[:1][0]),
|
||||
2,
|
||||
) == 0 {
|
||||
return signature, fmt.Errorf("failed to combine signatures")
|
||||
}
|
||||
|
||||
return signature, nil
|
||||
}
|
||||
|
||||
func ComputeMuunPartialSignature(
|
||||
data [32]byte,
|
||||
userKey *btcec.PublicKey,
|
||||
muunKey *btcec.PrivateKey,
|
||||
rawUserPubNonce [66]byte,
|
||||
sessionId [32]byte,
|
||||
customTweak []byte,
|
||||
) ([32]byte, error) {
|
||||
var rawPartialMuunSig [32]byte
|
||||
|
||||
// Safe C-interop rules require C pointer (ie the array) can't contain go
|
||||
// pointers. These go into an array, so we need to allocate manually.
|
||||
userXOnly := (*C.secp256k1_xonly_pubkey)(
|
||||
C.malloc(C.sizeof_secp256k1_xonly_pubkey),
|
||||
)
|
||||
defer C.free(unsafe.Pointer(userXOnly))
|
||||
|
||||
muunXOnly := (*C.secp256k1_xonly_pubkey)(
|
||||
C.malloc(C.sizeof_secp256k1_xonly_pubkey),
|
||||
)
|
||||
defer C.free(unsafe.Pointer(muunXOnly))
|
||||
|
||||
if C.secp256k1_xonly_pubkey_parse(
|
||||
ctx,
|
||||
userXOnly,
|
||||
toUchar(userKey.SerializeCompressed()[1:]),
|
||||
) == 0 {
|
||||
return rawPartialMuunSig, fmt.Errorf("failed to make xonly from user key")
|
||||
}
|
||||
|
||||
if C.secp256k1_xonly_pubkey_parse(
|
||||
ctx,
|
||||
muunXOnly,
|
||||
toUchar(muunKey.PubKey().SerializeCompressed()[1:]),
|
||||
) == 0 {
|
||||
return rawPartialMuunSig, fmt.Errorf("failed to make xonly from user key")
|
||||
}
|
||||
|
||||
keys := []*C.secp256k1_xonly_pubkey{
|
||||
userXOnly,
|
||||
muunXOnly,
|
||||
}
|
||||
|
||||
var combined C.secp256k1_xonly_pubkey
|
||||
var keyaggCache C.secp256k1_musig_keyagg_cache
|
||||
if C.secp256k1_musig_pubkey_agg(
|
||||
ctx,
|
||||
nil,
|
||||
&combined,
|
||||
&keyaggCache,
|
||||
(**C.secp256k1_xonly_pubkey)(&keys[:1][0]),
|
||||
2,
|
||||
) == 0 {
|
||||
return rawPartialMuunSig, fmt.Errorf("failed to combine keys")
|
||||
}
|
||||
|
||||
var secnonce C.secp256k1_musig_secnonce
|
||||
muunPubNonce := (*C.secp256k1_musig_pubnonce)(C.malloc(C.sizeof_secp256k1_musig_pubnonce))
|
||||
|
||||
if C.secp256k1_musig_nonce_gen(
|
||||
ctx,
|
||||
&secnonce,
|
||||
muunPubNonce,
|
||||
toUchar(sessionId[:]),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
) == 0 {
|
||||
return rawPartialMuunSig, fmt.Errorf("failed to create pre session")
|
||||
}
|
||||
|
||||
userPubNonce := (*C.secp256k1_musig_pubnonce)(C.malloc(C.sizeof_secp256k1_musig_pubnonce))
|
||||
defer C.free(unsafe.Pointer(userPubNonce))
|
||||
|
||||
if C.secp256k1_musig_pubnonce_parse(
|
||||
ctx,
|
||||
userPubNonce,
|
||||
toUchar(rawUserPubNonce[:]),
|
||||
) == 0 {
|
||||
return rawPartialMuunSig, fmt.Errorf("failed to parse muun pub nonce")
|
||||
}
|
||||
|
||||
tweak, err := tagTweakOrDefault(&combined, customTweak)
|
||||
if err != nil {
|
||||
return rawPartialMuunSig, err
|
||||
}
|
||||
|
||||
var tweakedPubKey C.secp256k1_pubkey
|
||||
if C.secp256k1_musig_pubkey_tweak_add(
|
||||
ctx,
|
||||
&tweakedPubKey,
|
||||
toUchar(tweak[:]),
|
||||
&keyaggCache,
|
||||
) == 0 {
|
||||
return rawPartialMuunSig, fmt.Errorf("failed to tweak key")
|
||||
}
|
||||
|
||||
// The API is kinda unhappy, and now requires us to transform the
|
||||
// tweaked pub key to x-only and overwrite the previous combined key
|
||||
if C.secp256k1_xonly_pubkey_from_pubkey(
|
||||
ctx,
|
||||
&combined,
|
||||
nil,
|
||||
&tweakedPubKey,
|
||||
) == 0 {
|
||||
return rawPartialMuunSig, fmt.Errorf("failed to transform tweaked key to xonly")
|
||||
}
|
||||
|
||||
var aggNonce C.secp256k1_musig_aggnonce
|
||||
|
||||
pubNonces := []*C.secp256k1_musig_pubnonce{
|
||||
userPubNonce,
|
||||
muunPubNonce,
|
||||
}
|
||||
if C.secp256k1_musig_nonce_agg(
|
||||
ctx,
|
||||
&aggNonce,
|
||||
(**C.secp256k1_musig_pubnonce)(&pubNonces[:1][0]),
|
||||
2,
|
||||
) == 0 {
|
||||
return rawPartialMuunSig, fmt.Errorf("failed to aggregate nonces")
|
||||
}
|
||||
|
||||
var session C.secp256k1_musig_session
|
||||
|
||||
if C.secp256k1_musig_nonce_process(
|
||||
ctx,
|
||||
&session,
|
||||
&aggNonce,
|
||||
toUchar(data[:]),
|
||||
&keyaggCache,
|
||||
nil,
|
||||
) == 0 {
|
||||
return rawPartialMuunSig, fmt.Errorf("failed to process nonces")
|
||||
}
|
||||
|
||||
var muunKeyPair C.secp256k1_keypair
|
||||
if C.secp256k1_keypair_create(
|
||||
ctx,
|
||||
&muunKeyPair,
|
||||
toUchar(muunKey.Serialize()),
|
||||
) == 0 {
|
||||
return rawPartialMuunSig, fmt.Errorf("failed to create user key pair")
|
||||
}
|
||||
|
||||
var muunPartialSig C.secp256k1_musig_partial_sig
|
||||
if C.secp256k1_musig_partial_sign(
|
||||
ctx,
|
||||
&muunPartialSig,
|
||||
&secnonce,
|
||||
&muunKeyPair,
|
||||
&keyaggCache,
|
||||
&session,
|
||||
) == 0 {
|
||||
return rawPartialMuunSig, fmt.Errorf("failed to sign with muun key")
|
||||
}
|
||||
|
||||
// Here to catch bugs!
|
||||
if C.secp256k1_musig_partial_sig_verify(
|
||||
ctx,
|
||||
&muunPartialSig,
|
||||
muunPubNonce,
|
||||
muunXOnly,
|
||||
&keyaggCache,
|
||||
&session,
|
||||
) == 0 {
|
||||
return rawPartialMuunSig, fmt.Errorf("partial sig is invalid")
|
||||
}
|
||||
|
||||
if C.secp256k1_musig_partial_sig_serialize(
|
||||
ctx,
|
||||
toUchar(rawPartialMuunSig[:]),
|
||||
&muunPartialSig,
|
||||
) == 0 {
|
||||
return rawPartialMuunSig, fmt.Errorf("failed to serialize partial sig")
|
||||
}
|
||||
|
||||
return rawPartialMuunSig, nil
|
||||
}
|
||||
|
||||
// VerifySignature checks a Schnorr signature.
|
||||
func VerifySignature(data [32]byte, signature [64]byte, pubKey *btcec.PublicKey) bool {
|
||||
var xOnly C.secp256k1_xonly_pubkey
|
||||
if C.secp256k1_xonly_pubkey_parse(
|
||||
ctx,
|
||||
&xOnly,
|
||||
toUchar(pubKey.SerializeCompressed()[1:]),
|
||||
) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return C.secp256k1_schnorrsig_verify(
|
||||
ctx,
|
||||
toUchar(signature[:]),
|
||||
toUchar(data[:]),
|
||||
32,
|
||||
&xOnly,
|
||||
) == 1
|
||||
}
|
||||
|
||||
func tagTweakOrDefault(pubKey *C.secp256k1_xonly_pubkey, customTweak []byte) ([32]byte, error) {
|
||||
var untaggedTweak []byte
|
||||
|
||||
if customTweak != nil {
|
||||
if len(customTweak) != 32 {
|
||||
return [32]byte{}, fmt.Errorf("tweak must be 32 bytes long, not %d", len(customTweak))
|
||||
}
|
||||
|
||||
var emptyTweak [32]byte
|
||||
if bytes.Equal(customTweak, emptyTweak[:]) {
|
||||
return [32]byte{}, fmt.Errorf("tweak can't be empty (zero-filled slice given)")
|
||||
}
|
||||
|
||||
untaggedTweak = customTweak[:]
|
||||
|
||||
} else {
|
||||
var serializedKey [32]byte
|
||||
if C.secp256k1_xonly_pubkey_serialize(
|
||||
ctx,
|
||||
toUchar(serializedKey[:]),
|
||||
pubKey,
|
||||
) == 0 {
|
||||
return [32]byte{}, fmt.Errorf("failed to serialize key to calculate default tweak")
|
||||
}
|
||||
|
||||
untaggedTweak = serializedKey[:]
|
||||
}
|
||||
|
||||
var taggedTweak [32]byte
|
||||
copy(taggedTweak[:], chainhashw.TaggedHashB(chainhashw.TagTapTweak, untaggedTweak))
|
||||
|
||||
return taggedTweak, nil
|
||||
}
|
||||
Reference in New Issue
Block a user