mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-18 12:44:19 +01:00
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:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
43
server/internal/core/application/covenantless_event.go
Normal file
43
server/internal/core/application/covenantless_event.go
Normal 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() {}
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user