mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-17 12:14:21 +01:00
Ark Notes (#379)
* ark credits * rename "ecash" --> "ark credit" * rework note_test.go * NewFromString * create several notes * note repo: rename "push" to "add" * RegisterInputsForNextRoundRequest: move "notes" to field #3 * use uint64 as note ID * rename to voucher * add nostr notification * nostr notification test and fixes * bump badger to 4.3 * allow npub to be registered * rename poolTxID * add default relays * Update server/internal/config/config.go Co-authored-by: Marco Argentieri <3596602+tiero@users.noreply.github.com> * fix RedeemVouchers test * notification = voucher * WASM wrappers * fix arkd voucher cmd * test_utils.go ignore gosec rule G101 * fix permissions * rename ALL to notes * add URI prefix * note.go : fix signature encoding * fix decode note.Data * Update server/internal/infrastructure/notifier/nostr/nostr.go Co-authored-by: Pietralberto Mazza <18440657+altafan@users.noreply.github.com> * Update pkg/client-sdk/wasm/browser/wrappers.go Co-authored-by: Pietralberto Mazza <18440657+altafan@users.noreply.github.com> * Update server/internal/infrastructure/notifier/nostr/nostr.go Co-authored-by: Pietralberto Mazza <18440657+altafan@users.noreply.github.com> * rework note and entity db + sqlite implementations * NOTIFICATION_PREFIX -> NOTE_URI_PREFIX * validate NOTE_URI_PREFIX * Update defaults to convenant-less mainnet (#2) * config: defaults to convenant-less tx builder * Drop env var for blockchain scanner --------- Co-authored-by: altafan <18440657+altafan@users.noreply.github.com> * add // before URI prefix * add URI prefix in admin CreateNote * Fixes * rework nonces encoding (#4) * rework nonces encoding * add a check in Musig2Nonce decode function * musig2_test: increase number of signers to 20 * musig2.json: add a test case with a 35 leaves tree * GetEventStream REST rework * fix round phases time intervals * [SDK] Use server-side streams in rest client * Fix history * make the URI optional * Updates * Fix settled txs in history * fix e2e test * go work sync in sdk unit test * fix signMessage in btc and liquid sdk wallets --------- Co-authored-by: Marco Argentieri <3596602+tiero@users.noreply.github.com> Co-authored-by: Pietralberto Mazza <18440657+altafan@users.noreply.github.com>
This commit is contained in:
@@ -7,16 +7,20 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ark-network/ark/common/note"
|
||||
"github.com/ark-network/ark/common/tree"
|
||||
"github.com/ark-network/ark/server/internal/core/domain"
|
||||
"github.com/ark-network/ark/server/internal/core/ports"
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type timedPayment struct {
|
||||
domain.Payment
|
||||
boardingInputs []ports.BoardingInput
|
||||
notes []note.Note
|
||||
timestamp time.Time
|
||||
pingTimestamp time.Time
|
||||
}
|
||||
@@ -59,6 +63,28 @@ func (m *paymentsMap) delete(id string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *paymentsMap) pushWithNotes(payment domain.Payment, notes []note.Note) error {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
|
||||
if _, ok := m.payments[payment.Id]; ok {
|
||||
return fmt.Errorf("duplicated payment %s", payment.Id)
|
||||
}
|
||||
|
||||
for _, note := range notes {
|
||||
for _, payment := range m.payments {
|
||||
for _, pNote := range payment.notes {
|
||||
if note.ID == pNote.ID {
|
||||
return fmt.Errorf("duplicated note %s", note)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m.payments[payment.Id] = &timedPayment{payment, make([]ports.BoardingInput, 0), notes, time.Now(), time.Time{}}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *paymentsMap) push(
|
||||
payment domain.Payment,
|
||||
boardingInputs []ports.BoardingInput,
|
||||
@@ -95,7 +121,7 @@ func (m *paymentsMap) push(
|
||||
m.descriptors[key] = desc
|
||||
}
|
||||
|
||||
m.payments[payment.Id] = &timedPayment{payment, boardingInputs, time.Now(), time.Time{}}
|
||||
m.payments[payment.Id] = &timedPayment{payment, boardingInputs, make([]note.Note, 0), time.Now(), time.Time{}}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -111,7 +137,7 @@ func (m *paymentsMap) pushEphemeralKey(paymentId string, pubkey *secp256k1.Publi
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *paymentsMap) pop(num int64) ([]domain.Payment, []ports.BoardingInput, map[domain.VtxoKey]string, []*secp256k1.PublicKey) {
|
||||
func (m *paymentsMap) pop(num int64) ([]domain.Payment, []ports.BoardingInput, map[domain.VtxoKey]string, []*secp256k1.PublicKey, []note.Note) {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
|
||||
@@ -139,6 +165,7 @@ func (m *paymentsMap) pop(num int64) ([]domain.Payment, []ports.BoardingInput, m
|
||||
boardingInputs := make([]ports.BoardingInput, 0)
|
||||
cosigners := make([]*secp256k1.PublicKey, 0, num)
|
||||
descriptors := make(map[domain.VtxoKey]string)
|
||||
notes := make([]note.Note, 0)
|
||||
for _, p := range paymentsByTime[:num] {
|
||||
boardingInputs = append(boardingInputs, p.boardingInputs...)
|
||||
payments = append(payments, p.Payment)
|
||||
@@ -146,13 +173,14 @@ func (m *paymentsMap) pop(num int64) ([]domain.Payment, []ports.BoardingInput, m
|
||||
cosigners = append(cosigners, pubkey)
|
||||
delete(m.ephemeralKeys, p.Payment.Id)
|
||||
}
|
||||
notes = append(notes, p.notes...)
|
||||
for _, vtxo := range p.Payment.Inputs {
|
||||
descriptors[vtxo.VtxoKey] = m.descriptors[vtxo.VtxoKey]
|
||||
delete(m.descriptors, vtxo.VtxoKey)
|
||||
}
|
||||
delete(m.payments, p.Id)
|
||||
}
|
||||
return payments, boardingInputs, descriptors, cosigners
|
||||
return payments, boardingInputs, descriptors, cosigners, notes
|
||||
}
|
||||
|
||||
func (m *paymentsMap) update(payment domain.Payment) error {
|
||||
@@ -164,6 +192,7 @@ func (m *paymentsMap) update(payment domain.Payment) error {
|
||||
return fmt.Errorf("payment %s not found", payment.Id)
|
||||
}
|
||||
|
||||
// sum inputs = vtxos + boarding utxos + notes
|
||||
sumOfInputs := uint64(0)
|
||||
for _, input := range payment.Inputs {
|
||||
sumOfInputs += input.Amount
|
||||
@@ -173,6 +202,11 @@ func (m *paymentsMap) update(payment domain.Payment) error {
|
||||
sumOfInputs += boardingInput.Amount
|
||||
}
|
||||
|
||||
for _, note := range p.notes {
|
||||
sumOfInputs += uint64(note.Value)
|
||||
}
|
||||
|
||||
// sum outputs = receivers VTXOs
|
||||
sumOfOutputs := uint64(0)
|
||||
for _, receiver := range payment.Receivers {
|
||||
sumOfOutputs += receiver.Amount
|
||||
@@ -369,3 +403,80 @@ func getSpentVtxos(payments map[string]domain.Payment) []domain.VtxoKey {
|
||||
}
|
||||
return vtxos
|
||||
}
|
||||
|
||||
func validateProofs(ctx context.Context, vtxoRepo domain.VtxoRepository, proofs []SignedVtxoOutpoint) error {
|
||||
for _, signedVtxo := range proofs {
|
||||
vtxos, err := vtxoRepo.GetVtxos(ctx, []domain.VtxoKey{signedVtxo.Outpoint})
|
||||
if err != nil {
|
||||
return fmt.Errorf("vtxo not found: %s (%s)", signedVtxo.Outpoint, err)
|
||||
}
|
||||
|
||||
if len(vtxos) < 1 {
|
||||
return fmt.Errorf("vtxo not found: %s", signedVtxo.Outpoint)
|
||||
}
|
||||
|
||||
vtxo := vtxos[0]
|
||||
|
||||
if err := signedVtxo.Proof.validate(vtxo); err != nil {
|
||||
return fmt.Errorf("invalid proof for vtxo %s (%s)", signedVtxo.Outpoint, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// nip19toNostrProfile decodes a NIP-19 string and returns a nostr profile
|
||||
// if nprofile => returns nostrRecipient
|
||||
// if npub => craft nprofile from npub and defaultRelays
|
||||
func nip19toNostrProfile(nostrRecipient string, defaultRelays []string) (string, error) {
|
||||
prefix, result, err := nip19.Decode(nostrRecipient)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to decode NIP-19 string: %s", err)
|
||||
}
|
||||
|
||||
var nprofileRecipient string
|
||||
|
||||
switch prefix {
|
||||
case "nprofile":
|
||||
recipient, ok := result.(nostr.ProfilePointer)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("invalid NIP-19 result: %v", result)
|
||||
}
|
||||
|
||||
// validate public key
|
||||
if !nostr.IsValidPublicKey(recipient.PublicKey) {
|
||||
return "", fmt.Errorf("invalid nostr public key: %s", recipient.PublicKey)
|
||||
}
|
||||
|
||||
// validate relays
|
||||
if len(recipient.Relays) == 0 {
|
||||
return "", fmt.Errorf("invalid nostr profile: at least one relay is required")
|
||||
}
|
||||
|
||||
for _, relay := range recipient.Relays {
|
||||
if !nostr.IsValidRelayURL(relay) {
|
||||
return "", fmt.Errorf("invalid relay URL: %s", relay)
|
||||
}
|
||||
}
|
||||
|
||||
nprofileRecipient = nostrRecipient
|
||||
case "npub":
|
||||
recipientPublicKey, ok := result.(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("invalid NIP-19 result: %v", result)
|
||||
}
|
||||
|
||||
nprofileRecipient, err = nip19.EncodeProfile(recipientPublicKey, defaultRelays)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to encode nostr profile: %s", err)
|
||||
}
|
||||
default:
|
||||
return "", fmt.Errorf("invalid NIP-19 prefix: %s", prefix)
|
||||
}
|
||||
|
||||
if nprofileRecipient == "" {
|
||||
return "", fmt.Errorf("invalid nostr recipient")
|
||||
}
|
||||
|
||||
return nprofileRecipient, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user