go-nostr/nip60/helpers.go
2025-01-28 16:48:44 -03:00

204 lines
5.0 KiB
Go

package nip60
import (
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/elnosh/gonuts/cashu"
"github.com/elnosh/gonuts/cashu/nuts/nut01"
"github.com/elnosh/gonuts/cashu/nuts/nut02"
"github.com/elnosh/gonuts/cashu/nuts/nut10"
"github.com/elnosh/gonuts/cashu/nuts/nut11"
"github.com/elnosh/gonuts/cashu/nuts/nut12"
"github.com/elnosh/gonuts/crypto"
)
func calculateFee(inputs cashu.Proofs, keysets []nut02.Keyset) uint64 {
var n uint = 0
next:
for _, proof := range inputs {
for _, ks := range keysets {
if ks.Id == proof.Id {
n += ks.InputFeePpk
continue next
}
}
panic(fmt.Errorf("spending a proof we don't have the keyset for? %v // %v", proof, keysets))
}
return uint64((n + 999) / 1000)
}
// returns blinded messages, secrets - [][]byte, and list of r
func createBlindedMessages(
splitAmounts []uint64,
keysetId string,
spendingCondition *nut10.SpendingCondition,
) (cashu.BlindedMessages, []string, []*secp256k1.PrivateKey, error) {
splitLen := len(splitAmounts)
blindedMessages := make(cashu.BlindedMessages, splitLen)
secrets := make([]string, splitLen)
rs := make([]*secp256k1.PrivateKey, splitLen)
for i, amt := range splitAmounts {
r, err := secp256k1.GeneratePrivateKey()
if err != nil {
return nil, nil, nil, err
}
var secret string
if spendingCondition != nil {
secret, err = nut10.NewSecretFromSpendingCondition(*spendingCondition)
if err != nil {
return nil, nil, nil, err
}
} else {
secretBytes := make([]byte, 32)
if _, err := rand.Read(secretBytes); err != nil {
return nil, nil, nil, err
}
secret = hex.EncodeToString(secretBytes)
}
B_, r, err := crypto.BlindMessage(secret, r)
if err != nil {
return nil, nil, nil, err
}
blindedMessages[i] = cashu.NewBlindedMessage(keysetId, amt, B_)
secrets[i] = secret
rs[i] = r
}
return blindedMessages, secrets, rs, nil
}
func signInput(
privateKey *btcec.PrivateKey,
proof cashu.Proof,
) (string, error) {
hash := sha256.Sum256([]byte(proof.Secret))
signature, err := schnorr.Sign(privateKey, hash[:])
if err != nil {
return "", fmt.Errorf("failed to sign: %w", err)
}
witness, _ := json.Marshal(nut11.P2PKWitness{
Signatures: []string{hex.EncodeToString(signature.Serialize())},
})
return string(witness), nil
}
func signOutput(
privateKey *btcec.PrivateKey,
output cashu.BlindedMessage,
) (string, error) {
msg, _ := hex.DecodeString(output.B_)
hash := sha256.Sum256(msg)
signature, err := schnorr.Sign(privateKey, hash[:])
if err != nil {
return "", fmt.Errorf("failed to sign: %w", err)
}
witness, _ := json.Marshal(nut11.P2PKWitness{
Signatures: []string{hex.EncodeToString(signature.Serialize())},
})
return string(witness), nil
}
// constructProofs unblinds the blindedSignatures and returns the proofs
func constructProofs(
blindedSignatures cashu.BlindedSignatures,
blindedMessages cashu.BlindedMessages,
secrets []string,
rs []*secp256k1.PrivateKey,
keys map[uint64]*btcec.PublicKey,
) (cashu.Proofs, error) {
sigsLenght := len(blindedSignatures)
if sigsLenght != len(secrets) || sigsLenght != len(rs) {
return nil, errors.New("lengths do not match")
}
proofs := make(cashu.Proofs, len(blindedSignatures))
for i, blindedSignature := range blindedSignatures {
pubkey, ok := keys[blindedSignature.Amount]
if !ok {
return nil, errors.New("key not found")
}
var dleq *cashu.DLEQProof
// verify DLEQ if present
if blindedSignature.DLEQ != nil {
if !nut12.VerifyBlindSignatureDLEQ(
*blindedSignature.DLEQ,
pubkey,
blindedMessages[i].B_,
blindedSignature.C_,
) {
return nil, errors.New("got blinded signature with invalid DLEQ proof")
} else {
dleq = &cashu.DLEQProof{
E: blindedSignature.DLEQ.E,
S: blindedSignature.DLEQ.S,
R: hex.EncodeToString(rs[i].Serialize()),
}
}
}
C, err := unblindSignature(blindedSignature.C_, rs[i], pubkey)
if err != nil {
return nil, err
}
proof := cashu.Proof{
Amount: blindedSignature.Amount,
Secret: secrets[i],
C: C,
Id: blindedSignature.Id,
DLEQ: dleq,
}
proofs[i] = proof
}
return proofs, nil
}
func unblindSignature(C_str string, r *secp256k1.PrivateKey, key *secp256k1.PublicKey) (
string,
error,
) {
C_bytes, err := hex.DecodeString(C_str)
if err != nil {
return "", err
}
C_, err := secp256k1.ParsePubKey(C_bytes)
if err != nil {
return "", err
}
C := crypto.UnblindSignature(C_, r, key)
Cstr := hex.EncodeToString(C.SerializeCompressed())
return Cstr, nil
}
func parseKeysetKeys(keys nut01.KeysMap) (map[uint64]*btcec.PublicKey, error) {
parsedKeys := make(map[uint64]*btcec.PublicKey)
for amount, pkh := range keys {
pkb, err := hex.DecodeString(pkh)
if err != nil {
return nil, err
}
pubkey, err := btcec.ParsePubKey(pkb)
if err != nil {
return nil, err
}
parsedKeys[amount] = pubkey
}
return parsedKeys, nil
}