Make the round participants sign the vtxo tree (#271)

* [proto] add APIs to send and receive musig2 signing data

* [common] add serialization functions for nonces and signatures

* [application] implements tree signing

* fix: remove old debug logs

* [proto] cleaning

* [common] fix musig2.go

* [application] fixes and logs

* [interface] fix: stop forwarding 2 times the events

* [client] add musig2 support + sign the tree when joining a round

* [interface] add new APIs into permissions.go

* [application][proto] rework PingResponse (return all events type)

* [common] split SetKeys into 2 distinct methods

* [client] fixes according to musig2.go changes

* [sdk] support tree signing + new PingResponse

* [sdk] fixes

* [application] revert event channel type

* [application] use domain.RoundEvent as lastEvent type

* [application] remove IsCovenantLess

* comments

* [application] revert roundAborted changes

* [interface] remove bitcointree dependencie
This commit is contained in:
Louis Singer
2024-08-30 14:32:35 +02:00
committed by GitHub
parent 1b9660ec89
commit c183f99244
40 changed files with 4143 additions and 713 deletions

View File

@@ -20,6 +20,10 @@ import (
"github.com/vulpemventures/go-elements/psetv2"
)
var (
ErrTreeSigningNotRequired = fmt.Errorf("tree signing is not required on this ark (covenant)")
)
type covenantService struct {
network common.Network
pubkey *secp256k1.PublicKey
@@ -40,6 +44,7 @@ type covenantService struct {
eventsCh chan domain.RoundEvent
onboardingCh chan onboarding
lastEvent domain.RoundEvent
currentRound *domain.Round
}
@@ -66,7 +71,7 @@ func NewCovenantService(
network, pubkey,
roundLifetime, roundInterval, unilateralExitDelay, minRelayFee,
walletSvc, repoManager, builder, scanner, sweeper,
paymentRequests, forfeitTxs, eventsCh, onboardingCh, nil,
paymentRequests, forfeitTxs, eventsCh, onboardingCh, nil, nil,
}
repoManager.RegisterEventsHandler(
func(round *domain.Round) {
@@ -148,17 +153,17 @@ func (s *covenantService) ClaimVtxos(ctx context.Context, creds string, receiver
return s.paymentRequests.update(*payment)
}
func (s *covenantService) UpdatePaymentStatus(_ context.Context, id string) ([]string, *domain.Round, error) {
func (s *covenantService) UpdatePaymentStatus(_ context.Context, id string) (domain.RoundEvent, error) {
err := s.paymentRequests.updatePingTimestamp(id)
if err != nil {
if _, ok := err.(errPaymentNotFound); ok {
return s.forfeitTxs.view(), s.currentRound, nil
return s.lastEvent, nil
}
return nil, nil, err
return nil, err
}
return nil, nil, nil
return s.lastEvent, nil
}
func (s *covenantService) CompleteAsyncPayment(ctx context.Context, redeemTx string, unconditionalForfeitTxs []string) error {
@@ -248,6 +253,24 @@ func (s *covenantService) Onboard(
return nil
}
func (s *covenantService) RegisterCosignerPubkey(ctx context.Context, paymentId string, _ string) error {
// if the user sends an ephemeral pubkey, something is going wrong client-side
// we should delete the associated payment
if err := s.paymentRequests.delete(paymentId); err != nil {
log.WithError(err).Warn("failed to delete payment")
}
return ErrTreeSigningNotRequired
}
func (s *covenantService) RegisterCosignerNonces(context.Context, string, *secp256k1.PublicKey, string) error {
return ErrTreeSigningNotRequired
}
func (s *covenantService) RegisterCosignerSignatures(context.Context, string, *secp256k1.PublicKey, string) error {
return ErrTreeSigningNotRequired
}
func (s *covenantService) start() {
s.startRound()
}
@@ -256,6 +279,7 @@ func (s *covenantService) startRound() {
round := domain.NewRound(dustAmount)
//nolint:all
round.StartRegistration()
s.lastEvent = nil
s.currentRound = round
defer func() {
@@ -305,7 +329,7 @@ func (s *covenantService) startFinalization() {
if num > paymentsThreshold {
num = paymentsThreshold
}
payments := s.paymentRequests.pop(num)
payments, _ := s.paymentRequests.pop(num)
if _, err := round.RegisterPayments(payments); err != nil {
round.Fail(fmt.Errorf("failed to register payments: %s", err))
log.WithError(err).Warn("failed to register payments")
@@ -670,14 +694,17 @@ func (s *covenantService) propagateEvents(round *domain.Round) {
switch e := lastEvent.(type) {
case domain.RoundFinalizationStarted:
forfeitTxs := s.forfeitTxs.view()
s.eventsCh <- domain.RoundFinalizationStarted{
ev := domain.RoundFinalizationStarted{
Id: e.Id,
CongestionTree: e.CongestionTree,
Connectors: e.Connectors,
PoolTx: e.PoolTx,
UnsignedForfeitTxs: forfeitTxs,
}
s.lastEvent = ev
s.eventsCh <- ev
case domain.RoundFinalized, domain.RoundFailed:
s.lastEvent = e
s.eventsCh <- e
}
}

View File

@@ -41,9 +41,11 @@ type covenantlessService struct {
eventsCh chan domain.RoundEvent
onboardingCh chan onboarding
currentRound *domain.Round
asyncPaymentsCache map[domain.VtxoKey]struct {
// cached data for the current round
lastEvent domain.RoundEvent
currentRound *domain.Round
treeSigningSessions map[string]*musigSigningSession
asyncPaymentsCache map[domain.VtxoKey]struct {
receivers []domain.Receiver
expireAt int64
}
@@ -89,6 +91,7 @@ func NewCovenantlessService(
eventsCh: eventsCh,
onboardingCh: onboardingCh,
asyncPaymentsCache: asyncPaymentsCache,
treeSigningSessions: make(map[string]*musigSigningSession),
}
repoManager.RegisterEventsHandler(
@@ -273,17 +276,17 @@ func (s *covenantlessService) ClaimVtxos(ctx context.Context, creds string, rece
return s.paymentRequests.update(*payment)
}
func (s *covenantlessService) UpdatePaymentStatus(_ context.Context, id string) ([]string, *domain.Round, error) {
func (s *covenantlessService) UpdatePaymentStatus(_ context.Context, id string) (domain.RoundEvent, error) {
err := s.paymentRequests.updatePingTimestamp(id)
if err != nil {
if _, ok := err.(errPaymentNotFound); ok {
return s.forfeitTxs.view(), s.currentRound, nil
return s.lastEvent, nil
}
return nil, nil, err
return nil, err
}
return nil, nil, nil
return s.lastEvent, nil
}
func (s *covenantlessService) SignVtxos(ctx context.Context, forfeitTxs []string) error {
@@ -367,6 +370,78 @@ func (s *covenantlessService) Onboard(
return nil
}
func (s *covenantlessService) RegisterCosignerPubkey(ctx context.Context, paymentId string, pubkey string) error {
pubkeyBytes, err := hex.DecodeString(pubkey)
if err != nil {
return fmt.Errorf("failed to decode hex pubkey: %s", err)
}
ephemeralPublicKey, err := secp256k1.ParsePubKey(pubkeyBytes)
if err != nil {
return fmt.Errorf("failed to parse pubkey: %s", err)
}
return s.paymentRequests.pushEphemeralKey(paymentId, ephemeralPublicKey)
}
func (s *covenantlessService) RegisterCosignerNonces(
ctx context.Context, roundID string, pubkey *secp256k1.PublicKey, encodedNonces string,
) error {
session, ok := s.treeSigningSessions[roundID]
if !ok {
return fmt.Errorf(`signing session not found for round "%s"`, roundID)
}
nonces, err := bitcointree.DecodeNonces(hex.NewDecoder(strings.NewReader(encodedNonces)))
if err != nil {
return fmt.Errorf("failed to decode nonces: %s", err)
}
session.lock.Lock()
defer session.lock.Unlock()
if _, ok := session.nonces[pubkey]; ok {
return nil // skip if we already have nonces for this pubkey
}
session.nonces[pubkey] = nonces
if len(session.nonces) == session.nbCosigners-1 { // exclude the ASP
session.nonceDoneC <- struct{}{}
}
return nil
}
func (s *covenantlessService) RegisterCosignerSignatures(
ctx context.Context, roundID string, pubkey *secp256k1.PublicKey, encodedSignatures string,
) error {
session, ok := s.treeSigningSessions[roundID]
if !ok {
return fmt.Errorf(`signing session not found for round "%s"`, roundID)
}
signatures, err := bitcointree.DecodeSignatures(hex.NewDecoder(strings.NewReader(encodedSignatures)))
if err != nil {
return fmt.Errorf("failed to decode signatures: %s", err)
}
session.lock.Lock()
defer session.lock.Unlock()
if _, ok := session.signatures[pubkey]; ok {
return nil // skip if we already have signatures for this pubkey
}
session.signatures[pubkey] = signatures
if len(session.signatures) == session.nbCosigners-1 { // exclude the ASP
session.sigDoneC <- struct{}{}
}
return nil
}
func (s *covenantlessService) start() {
s.startRound()
}
@@ -375,6 +450,7 @@ func (s *covenantlessService) startRound() {
round := domain.NewRound(dustAmount) // TODO dynamic dust amount?
//nolint:all
round.StartRegistration()
s.lastEvent = nil
s.currentRound = round
defer func() {
@@ -389,8 +465,12 @@ func (s *covenantlessService) startFinalization() {
ctx := context.Background()
round := s.currentRound
roundRemainingDuration := time.Duration(s.roundInterval/2-1) * time.Second
thirdOfRemainingDuration := time.Duration(roundRemainingDuration / 3)
var roundAborted bool
defer func() {
delete(s.treeSigningSessions, round.Id)
if roundAborted {
s.startRound()
return
@@ -404,7 +484,7 @@ func (s *covenantlessService) startFinalization() {
s.startRound()
return
}
time.Sleep(time.Duration((s.roundInterval/2)-1) * time.Second)
time.Sleep(thirdOfRemainingDuration)
s.finalizeRound()
}()
@@ -424,7 +504,14 @@ func (s *covenantlessService) startFinalization() {
if num > paymentsThreshold {
num = paymentsThreshold
}
payments := s.paymentRequests.pop(num)
payments, cosigners := s.paymentRequests.pop(num)
if len(payments) > len(cosigners) {
err := fmt.Errorf("missing ephemeral key for payments")
round.Fail(fmt.Errorf("round aborted: %s", err))
log.WithError(err).Debugf("round %s aborted", round.Id)
return
}
if _, err := round.RegisterPayments(payments); err != nil {
round.Fail(fmt.Errorf("failed to register payments: %s", err))
log.WithError(err).Warn("failed to register payments")
@@ -438,32 +525,16 @@ func (s *covenantlessService) startFinalization() {
return
}
cosigners := make([]*secp256k1.PrivateKey, 0)
cosignersPubKeys := make([]*secp256k1.PublicKey, 0, len(cosigners))
for range payments {
// TODO sender should provide the ephemeral *public* key
ephemeralKey, err := secp256k1.GeneratePrivateKey()
if err != nil {
round.Fail(fmt.Errorf("failed to generate ephemeral key: %s", err))
log.WithError(err).Warn("failed to generate ephemeral key")
return
}
cosigners = append(cosigners, ephemeralKey)
cosignersPubKeys = append(cosignersPubKeys, ephemeralKey.PubKey())
}
aspSigningKey, err := secp256k1.GeneratePrivateKey()
ephemeralKey, err := secp256k1.GeneratePrivateKey()
if err != nil {
round.Fail(fmt.Errorf("failed to generate asp signing key: %s", err))
log.WithError(err).Warn("failed to generate asp signing key")
round.Fail(fmt.Errorf("failed to generate ephemeral key: %s", err))
log.WithError(err).Warn("failed to generate ephemeral key")
return
}
cosigners = append(cosigners, aspSigningKey)
cosignersPubKeys = append(cosignersPubKeys, aspSigningKey.PubKey())
cosigners = append(cosigners, ephemeralKey.PubKey())
unsignedPoolTx, tree, connectorAddress, err := s.builder.BuildPoolTx(s.pubkey, payments, s.minRelayFee, sweptRounds, cosignersPubKeys...)
unsignedPoolTx, tree, connectorAddress, err := s.builder.BuildPoolTx(s.pubkey, payments, s.minRelayFee, sweptRounds, cosigners...)
if err != nil {
round.Fail(fmt.Errorf("failed to create pool tx: %s", err))
log.WithError(err).Warn("failed to create pool tx")
@@ -472,6 +543,16 @@ func (s *covenantlessService) startFinalization() {
log.Debugf("pool tx created for round %s", round.Id)
if len(tree) > 0 {
log.Debugf("signing congestion tree for round %s", round.Id)
signingSession := newMusigSigningSession(len(cosigners))
s.treeSigningSessions[round.Id] = signingSession
log.Debugf("signing session created for round %s", round.Id)
// send back the unsigned tree & all cosigners pubkeys
s.propagateRoundSigningStartedEvent(tree, cosigners)
sweepClosure := bitcointree.CSVSigClosure{
Pubkey: s.pubkey,
Seconds: uint(s.roundLifetime),
@@ -485,37 +566,50 @@ func (s *covenantlessService) startFinalization() {
sweepTapTree := txscript.AssembleTaprootScriptTree(*sweepTapLeaf)
root := sweepTapTree.RootNode.TapHash()
coordinator, err := s.createTreeCoordinatorSession(tree, cosignersPubKeys, root)
coordinator, err := s.createTreeCoordinatorSession(tree, cosigners, root)
if err != nil {
round.Fail(fmt.Errorf("failed to create tree coordinator: %s", err))
log.WithError(err).Warn("failed to create tree coordinator")
return
}
signers := make([]bitcointree.SignerSession, 0)
aspSignerSession := bitcointree.NewTreeSignerSession(
ephemeralKey, tree, int64(s.minRelayFee), root.CloneBytes(),
)
for _, seckey := range cosigners {
signer := bitcointree.NewTreeSignerSession(
seckey, tree, int64(s.minRelayFee), root.CloneBytes(),
)
// TODO nonces should be sent by the sender
nonces, err := signer.GetNonces()
if err != nil {
round.Fail(fmt.Errorf("failed to get nonces: %s", err))
log.WithError(err).Warn("failed to get nonces")
return
}
if err := coordinator.AddNonce(seckey.PubKey(), nonces); err != nil {
round.Fail(fmt.Errorf("failed to add nonce: %s", err))
log.WithError(err).Warn("failed to add nonce")
return
}
signers = append(signers, signer)
nonces, err := aspSignerSession.GetNonces()
if err != nil {
round.Fail(fmt.Errorf("failed to get nonces: %s", err))
log.WithError(err).Warn("failed to get nonces")
return
}
if err := coordinator.AddNonce(ephemeralKey.PubKey(), nonces); err != nil {
round.Fail(fmt.Errorf("failed to add nonce: %s", err))
log.WithError(err).Warn("failed to add nonce")
return
}
noncesTimer := time.NewTimer(thirdOfRemainingDuration)
select {
case <-noncesTimer.C:
round.Fail(fmt.Errorf("musig2 signing session timed out (nonce collection)"))
log.Warn("musig2 signing session timed out (nonce collection)")
return
case <-signingSession.nonceDoneC:
noncesTimer.Stop()
for pubkey, nonce := range signingSession.nonces {
if err := coordinator.AddNonce(pubkey, nonce); err != nil {
round.Fail(fmt.Errorf("failed to add nonce: %s", err))
log.WithError(err).Warn("failed to add nonce")
return
}
}
}
log.Debugf("nonces collected for round %s", round.Id)
aggragatedNonces, err := coordinator.AggregateNonces()
if err != nil {
round.Fail(fmt.Errorf("failed to aggregate nonces: %s", err))
@@ -523,36 +617,69 @@ func (s *covenantlessService) startFinalization() {
return
}
// TODO aggragated nonces and public keys should be sent back to signer
// TODO signing should be done client-side (except for the ASP)
for i, signer := range signers {
if err := signer.SetKeys(cosignersPubKeys, aggragatedNonces); err != nil {
round.Fail(fmt.Errorf("failed to set keys: %s", err))
log.WithError(err).Warn("failed to set keys")
return
}
log.Debugf("nonces aggregated for round %s", round.Id)
sig, err := signer.Sign()
if err != nil {
round.Fail(fmt.Errorf("failed to sign: %s", err))
log.WithError(err).Warn("failed to sign")
return
}
s.propagateRoundSigningNoncesGeneratedEvent(aggragatedNonces)
if err := coordinator.AddSig(cosignersPubKeys[i], sig); err != nil {
round.Fail(fmt.Errorf("failed to add sig: %s", err))
log.WithError(err).Warn("failed to add sig")
return
}
if err := aspSignerSession.SetKeys(cosigners); err != nil {
round.Fail(fmt.Errorf("failed to set keys: %s", err))
log.WithError(err).Warn("failed to set keys")
return
}
signedTree, err := coordinator.SignTree()
if err := aspSignerSession.SetAggregatedNonces(aggragatedNonces); err != nil {
round.Fail(fmt.Errorf("failed to set aggregated nonces: %s", err))
log.WithError(err).Warn("failed to set aggregated nonces")
return
}
// sign the tree as ASP
aspTreeSigs, err := aspSignerSession.Sign()
if err != nil {
round.Fail(fmt.Errorf("failed to sign tree: %s", err))
log.WithError(err).Warn("failed to sign tree")
return
}
if err := coordinator.AddSig(ephemeralKey.PubKey(), aspTreeSigs); err != nil {
round.Fail(fmt.Errorf("failed to add signature: %s", err))
log.WithError(err).Warn("failed to add signature")
return
}
log.Debugf("ASP tree signed for round %s", round.Id)
signaturesTimer := time.NewTimer(thirdOfRemainingDuration)
log.Debugf("waiting for cosigners to sign the tree")
select {
case <-signaturesTimer.C:
round.Fail(fmt.Errorf("musig2 signing session timed out (signatures)"))
log.Warn("musig2 signing session timed out (signatures)")
return
case <-signingSession.sigDoneC:
signaturesTimer.Stop()
for pubkey, sig := range signingSession.signatures {
if err := coordinator.AddSig(pubkey, sig); err != nil {
round.Fail(fmt.Errorf("failed to add signature: %s", err))
log.WithError(err).Warn("failed to add signature")
return
}
}
}
log.Debugf("signatures collected for round %s", round.Id)
signedTree, err := coordinator.SignTree()
if err != nil {
round.Fail(fmt.Errorf("failed to aggragate tree signatures: %s", err))
log.WithError(err).Warn("failed aggragate tree signatures")
return
}
log.Debugf("congestion tree signed for round %s", round.Id)
tree = signedTree
}
@@ -578,6 +705,29 @@ func (s *covenantlessService) startFinalization() {
log.Debugf("started finalization stage for round: %s", round.Id)
}
func (s *covenantlessService) propagateRoundSigningStartedEvent(
unsignedCongestionTree tree.CongestionTree, cosigners []*secp256k1.PublicKey,
) {
ev := RoundSigningStarted{
Id: s.currentRound.Id,
UnsignedVtxoTree: unsignedCongestionTree,
Cosigners: cosigners,
}
s.lastEvent = ev
s.eventsCh <- ev
}
func (s *covenantlessService) propagateRoundSigningNoncesGeneratedEvent(combinedNonces bitcointree.TreeNonces) {
ev := RoundSigningNoncesGenerated{
Id: s.currentRound.Id,
Nonces: combinedNonces,
}
s.lastEvent = ev
s.eventsCh <- ev
}
func (s *covenantlessService) createTreeCoordinatorSession(
congestionTree tree.CongestionTree, cosigners []*secp256k1.PublicKey, root chainhash.Hash,
) (bitcointree.CoordinatorSession, error) {
@@ -900,14 +1050,17 @@ func (s *covenantlessService) propagateEvents(round *domain.Round) {
switch e := lastEvent.(type) {
case domain.RoundFinalizationStarted:
forfeitTxs := s.forfeitTxs.view()
s.eventsCh <- domain.RoundFinalizationStarted{
ev := domain.RoundFinalizationStarted{
Id: e.Id,
CongestionTree: e.CongestionTree,
Connectors: e.Connectors,
PoolTx: e.PoolTx,
UnsignedForfeitTxs: forfeitTxs,
}
s.lastEvent = ev
s.eventsCh <- ev
case domain.RoundFinalized, domain.RoundFailed:
s.lastEvent = e
s.eventsCh <- e
}
}
@@ -1118,3 +1271,26 @@ func findForfeitTxBitcoin(
return "", fmt.Errorf("forfeit tx not found")
}
// musigSigningSession holds the state of ephemeral nonces and signatures in order to coordinate the signing of the tree
type musigSigningSession struct {
lock sync.Mutex
nbCosigners int
nonces map[*secp256k1.PublicKey]bitcointree.TreeNonces
nonceDoneC chan struct{}
signatures map[*secp256k1.PublicKey]bitcointree.TreePartialSigs
sigDoneC chan struct{}
}
func newMusigSigningSession(nbCosigners int) *musigSigningSession {
return &musigSigningSession{
nonces: make(map[*secp256k1.PublicKey]bitcointree.TreeNonces),
nonceDoneC: make(chan struct{}),
signatures: make(map[*secp256k1.PublicKey]bitcointree.TreePartialSigs),
sigDoneC: make(chan struct{}),
lock: sync.Mutex{},
nbCosigners: nbCosigners,
}
}

View File

@@ -0,0 +1,43 @@
/*
* This package contains intermediary events that are used only by the covenantless version
* they let to sign the congestion tree using musig2 algorithm
* they are not included in domain because they don't mutate the Round state and should not be persisted
*/
package application
import (
"bytes"
"encoding/hex"
"github.com/ark-network/ark/common/bitcointree"
"github.com/ark-network/ark/common/tree"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
)
// signer should react to this event by generating a musig2 nonce for each transaction in the tree
type RoundSigningStarted struct {
Id string
UnsignedVtxoTree tree.CongestionTree
Cosigners []*secp256k1.PublicKey
}
// signer should react to this event by partially signing the vtxo tree transactions
// then, delete its ephemeral key
type RoundSigningNoncesGenerated struct {
Id string
Nonces bitcointree.TreeNonces // aggregated nonces
}
func (e RoundSigningNoncesGenerated) SerializeNonces() (string, error) {
var serialized bytes.Buffer
if err := e.Nonces.Encode(&serialized); err != nil {
return "", err
}
return hex.EncodeToString(serialized.Bytes()), nil
}
// implement domain.RoundEvent interface
func (r RoundSigningStarted) IsEvent() {}
func (r RoundSigningNoncesGenerated) IsEvent() {}

View File

@@ -149,7 +149,6 @@ func (s *sweeper) createTask(
for _, input := range inputs {
// sweepableVtxos related to the sweep input
sweepableVtxos := make([]domain.VtxoKey, 0)
fmt.Println("input", input.GetHash().String(), input.GetIndex())
// check if input is the vtxo itself
vtxos, _ := s.repoManager.Vtxos().GetVtxos(

View File

@@ -25,7 +25,7 @@ type Service interface {
GetEventsChannel(ctx context.Context) <-chan domain.RoundEvent
UpdatePaymentStatus(
ctx context.Context, paymentId string,
) (unsignedForfeitTxs []string, currentRound *domain.Round, err error)
) (lastEvent domain.RoundEvent, err error)
ListVtxos(
ctx context.Context, pubkey *secp256k1.PublicKey,
) (spendableVtxos, spentVtxos []domain.Vtxo, err error)
@@ -41,6 +41,16 @@ type Service interface {
CompleteAsyncPayment(
ctx context.Context, redeemTx string, unconditionalForfeitTxs []string,
) error
// Tree signing methods
RegisterCosignerPubkey(ctx context.Context, paymentId string, ephemeralPublicKey string) error
RegisterCosignerNonces(
ctx context.Context, roundID string,
pubkey *secp256k1.PublicKey, nonces string,
) error
RegisterCosignerSignatures(
ctx context.Context, roundID string,
pubkey *secp256k1.PublicKey, signatures string,
) error
}
type ServiceInfo struct {

View File

@@ -10,6 +10,7 @@ import (
"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/sirupsen/logrus"
)
@@ -20,8 +21,9 @@ type timedPayment struct {
}
type paymentsMap struct {
lock *sync.RWMutex
payments map[string]*timedPayment
lock *sync.RWMutex
payments map[string]*timedPayment
ephemeralKeys map[string]*secp256k1.PublicKey
}
func newPaymentsMap(payments []domain.Payment) *paymentsMap {
@@ -30,7 +32,7 @@ func newPaymentsMap(payments []domain.Payment) *paymentsMap {
paymentsById[p.Id] = &timedPayment{p, time.Now(), time.Time{}}
}
lock := &sync.RWMutex{}
return &paymentsMap{lock, paymentsById}
return &paymentsMap{lock, paymentsById, make(map[string]*secp256k1.PublicKey)}
}
func (m *paymentsMap) len() int64 {
@@ -46,6 +48,18 @@ func (m *paymentsMap) len() int64 {
return count
}
func (m *paymentsMap) delete(id string) error {
m.lock.Lock()
defer m.lock.Unlock()
if _, ok := m.payments[id]; !ok {
return errPaymentNotFound{id}
}
delete(m.payments, id)
return nil
}
func (m *paymentsMap) push(payment domain.Payment) error {
m.lock.Lock()
defer m.lock.Unlock()
@@ -58,7 +72,19 @@ func (m *paymentsMap) push(payment domain.Payment) error {
return nil
}
func (m *paymentsMap) pop(num int64) []domain.Payment {
func (m *paymentsMap) pushEphemeralKey(paymentId string, pubkey *secp256k1.PublicKey) error {
m.lock.Lock()
defer m.lock.Unlock()
if _, ok := m.payments[paymentId]; !ok {
return fmt.Errorf("payment %s not found, cannot register signing ephemeral public key", paymentId)
}
m.ephemeralKeys[paymentId] = pubkey
return nil
}
func (m *paymentsMap) pop(num int64) ([]domain.Payment, []*secp256k1.PublicKey) {
m.lock.Lock()
defer m.lock.Unlock()
@@ -83,11 +109,16 @@ func (m *paymentsMap) pop(num int64) []domain.Payment {
}
payments := make([]domain.Payment, 0, num)
cosigners := make([]*secp256k1.PublicKey, 0, num)
for _, p := range paymentsByTime[:num] {
payments = append(payments, p.Payment)
if pubkey, ok := m.ephemeralKeys[p.Payment.Id]; ok {
cosigners = append(cosigners, pubkey)
delete(m.ephemeralKeys, p.Payment.Id)
}
delete(m.payments, p.Id)
}
return payments
return payments, cosigners
}
func (m *paymentsMap) update(payment domain.Payment) error {