mirror of
https://github.com/muun/recovery.git
synced 2025-02-23 11:32:33 -05:00
593 lines
14 KiB
Go
593 lines
14 KiB
Go
|
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
|
||
|
}
|