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

@@ -261,6 +261,70 @@
]
}
},
"/v1/payment/tree/nonces": {
"post": {
"operationId": "ArkService_SendTreeNonces",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1SendTreeNoncesResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1SendTreeNoncesRequest"
}
}
],
"tags": [
"ArkService"
]
}
},
"/v1/payment/tree/signatures": {
"post": {
"operationId": "ArkService_SendTreeSignatures",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1SendTreeSignaturesResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1SendTreeSignaturesRequest"
}
}
],
"tags": [
"ArkService"
]
}
},
"/v1/ping/{paymentId}": {
"get": {
"operationId": "ArkService_Ping",
@@ -323,7 +387,6 @@
},
"/v1/round/{txid}": {
"get": {
"summary": "TODO BTC: signTree rpc",
"operationId": "ArkService_GetRound",
"responses": {
"200": {
@@ -502,14 +565,19 @@
"type": "object",
"properties": {
"roundFinalization": {
"$ref": "#/definitions/v1RoundFinalizationEvent",
"title": "TODO: BTC add \"signTree\" event"
"$ref": "#/definitions/v1RoundFinalizationEvent"
},
"roundFinalized": {
"$ref": "#/definitions/v1RoundFinalizedEvent"
},
"roundFailed": {
"$ref": "#/definitions/v1RoundFailed"
},
"roundSigning": {
"$ref": "#/definitions/v1RoundSigningEvent"
},
"roundSigningNoncesGenerated": {
"$ref": "#/definitions/v1RoundSigningNoncesGeneratedEvent"
}
}
},
@@ -649,14 +717,20 @@
"v1PingResponse": {
"type": "object",
"properties": {
"forfeitTxs": {
"type": "array",
"items": {
"type": "string"
}
},
"event": {
"roundFinalization": {
"$ref": "#/definitions/v1RoundFinalizationEvent"
},
"roundFinalized": {
"$ref": "#/definitions/v1RoundFinalizedEvent"
},
"roundFailed": {
"$ref": "#/definitions/v1RoundFailed"
},
"roundSigning": {
"$ref": "#/definitions/v1RoundSigningEvent"
},
"roundSigningNoncesGenerated": {
"$ref": "#/definitions/v1RoundSigningNoncesGeneratedEvent"
}
}
},
@@ -669,6 +743,9 @@
"type": "object",
"$ref": "#/definitions/v1Input"
}
},
"ephemeralPubkey": {
"type": "string"
}
}
},
@@ -766,6 +843,34 @@
}
}
},
"v1RoundSigningEvent": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"cosignersPubkeys": {
"type": "array",
"items": {
"type": "string"
}
},
"unsignedTree": {
"$ref": "#/definitions/v1Tree"
}
}
},
"v1RoundSigningNoncesGeneratedEvent": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"treeNonces": {
"type": "string"
}
}
},
"v1RoundStage": {
"type": "string",
"enum": [
@@ -777,6 +882,40 @@
],
"default": "ROUND_STAGE_UNSPECIFIED"
},
"v1SendTreeNoncesRequest": {
"type": "object",
"properties": {
"roundId": {
"type": "string"
},
"publicKey": {
"type": "string"
},
"treeNonces": {
"type": "string"
}
}
},
"v1SendTreeNoncesResponse": {
"type": "object"
},
"v1SendTreeSignaturesRequest": {
"type": "object",
"properties": {
"roundId": {
"type": "string"
},
"publicKey": {
"type": "string"
},
"treeSignatures": {
"type": "string"
}
}
},
"v1SendTreeSignaturesResponse": {
"type": "object"
},
"v1Tree": {
"type": "object",
"properties": {

View File

@@ -17,13 +17,24 @@ service ArkService {
body: "*"
};
};
rpc SendTreeNonces(SendTreeNoncesRequest) returns (SendTreeNoncesResponse) {
option (google.api.http) = {
post: "/v1/payment/tree/nonces"
body: "*"
};
}
rpc SendTreeSignatures(SendTreeSignaturesRequest) returns (SendTreeSignaturesResponse) {
option (google.api.http) = {
post: "/v1/payment/tree/signatures"
body: "*"
};
}
rpc FinalizePayment(FinalizePaymentRequest) returns (FinalizePaymentResponse) {
option (google.api.http) = {
post: "/v1/payment/finalize"
body: "*"
};
};
// TODO BTC: signTree rpc
rpc GetRound(GetRoundRequest) returns (GetRoundResponse) {
option (google.api.http) = {
get: "/v1/round/{txid}"
@@ -91,6 +102,7 @@ message CompletePaymentResponse {}
message RegisterPaymentRequest {
repeated Input inputs = 1;
optional string ephemeral_pubkey = 2;
}
message RegisterPaymentResponse {
// Mocks wabisabi's credentials.
@@ -128,10 +140,11 @@ message GetRoundByIdResponse {
message GetEventStreamRequest {}
message GetEventStreamResponse {
oneof event {
// TODO: BTC add "signTree" event
RoundFinalizationEvent round_finalization = 1;
RoundFinalizedEvent round_finalized = 2;
RoundFailed round_failed = 3;
RoundSigningEvent round_signing = 4;
RoundSigningNoncesGeneratedEvent round_signing_nonces_generated = 5;
}
}
@@ -139,8 +152,13 @@ message PingRequest {
string payment_id = 1;
}
message PingResponse {
repeated string forfeit_txs = 1;
RoundFinalizationEvent event = 2;
oneof event {
RoundFinalizationEvent round_finalization = 1;
RoundFinalizedEvent round_finalized = 2;
RoundFailed round_failed = 3;
RoundSigningEvent round_signing = 4;
RoundSigningNoncesGeneratedEvent round_signing_nonces_generated = 5;
}
}
message ListVtxosRequest {
@@ -189,6 +207,17 @@ message RoundFailed {
string reason = 2;
}
message RoundSigningEvent {
string id = 1;
repeated string cosigners_pubkeys = 2;
Tree unsigned_tree = 3;
}
message RoundSigningNoncesGeneratedEvent {
string id = 1;
string tree_nonces = 2;
}
// TYPES
enum RoundStage {
@@ -252,3 +281,19 @@ message PendingPayment {
string redeem_tx = 1;
repeated string unconditional_forfeit_txs =2;
}
message SendTreeNoncesRequest {
string round_id = 1;
string public_key = 2;
string tree_nonces = 3;
}
message SendTreeNoncesResponse {}
message SendTreeSignaturesRequest {
string round_id = 1;
string public_key = 2;
string tree_signatures = 3;
}
message SendTreeSignaturesResponse {}

File diff suppressed because it is too large Load Diff

View File

@@ -83,6 +83,58 @@ func local_request_ArkService_ClaimPayment_0(ctx context.Context, marshaler runt
}
func request_ArkService_SendTreeNonces_0(ctx context.Context, marshaler runtime.Marshaler, client ArkServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SendTreeNoncesRequest
var metadata runtime.ServerMetadata
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.SendTreeNonces(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_ArkService_SendTreeNonces_0(ctx context.Context, marshaler runtime.Marshaler, server ArkServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SendTreeNoncesRequest
var metadata runtime.ServerMetadata
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.SendTreeNonces(ctx, &protoReq)
return msg, metadata, err
}
func request_ArkService_SendTreeSignatures_0(ctx context.Context, marshaler runtime.Marshaler, client ArkServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SendTreeSignaturesRequest
var metadata runtime.ServerMetadata
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.SendTreeSignatures(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_ArkService_SendTreeSignatures_0(ctx context.Context, marshaler runtime.Marshaler, server ArkServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SendTreeSignaturesRequest
var metadata runtime.ServerMetadata
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.SendTreeSignatures(ctx, &protoReq)
return msg, metadata, err
}
func request_ArkService_FinalizePayment_0(ctx context.Context, marshaler runtime.Marshaler, client ArkServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq FinalizePaymentRequest
var metadata runtime.ServerMetadata
@@ -486,6 +538,56 @@ func RegisterArkServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux,
})
mux.Handle("POST", pattern_ArkService_SendTreeNonces_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/ark.v1.ArkService/SendTreeNonces", runtime.WithHTTPPathPattern("/v1/payment/tree/nonces"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_ArkService_SendTreeNonces_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ArkService_SendTreeNonces_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_ArkService_SendTreeSignatures_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/ark.v1.ArkService/SendTreeSignatures", runtime.WithHTTPPathPattern("/v1/payment/tree/signatures"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_ArkService_SendTreeSignatures_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ArkService_SendTreeSignatures_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_ArkService_FinalizePayment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@@ -803,6 +905,50 @@ func RegisterArkServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux,
})
mux.Handle("POST", pattern_ArkService_SendTreeNonces_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ark.v1.ArkService/SendTreeNonces", runtime.WithHTTPPathPattern("/v1/payment/tree/nonces"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ArkService_SendTreeNonces_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ArkService_SendTreeNonces_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_ArkService_SendTreeSignatures_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ark.v1.ArkService/SendTreeSignatures", runtime.WithHTTPPathPattern("/v1/payment/tree/signatures"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ArkService_SendTreeSignatures_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ArkService_SendTreeSignatures_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_ArkService_FinalizePayment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@@ -1031,6 +1177,10 @@ var (
pattern_ArkService_ClaimPayment_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "payment", "claim"}, ""))
pattern_ArkService_SendTreeNonces_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v1", "payment", "tree", "nonces"}, ""))
pattern_ArkService_SendTreeSignatures_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v1", "payment", "tree", "signatures"}, ""))
pattern_ArkService_FinalizePayment_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "payment", "finalize"}, ""))
pattern_ArkService_GetRound_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "round", "txid"}, ""))
@@ -1057,6 +1207,10 @@ var (
forward_ArkService_ClaimPayment_0 = runtime.ForwardResponseMessage
forward_ArkService_SendTreeNonces_0 = runtime.ForwardResponseMessage
forward_ArkService_SendTreeSignatures_0 = runtime.ForwardResponseMessage
forward_ArkService_FinalizePayment_0 = runtime.ForwardResponseMessage
forward_ArkService_GetRound_0 = runtime.ForwardResponseMessage

View File

@@ -20,8 +20,9 @@ const _ = grpc.SupportPackageIsVersion7
type ArkServiceClient interface {
RegisterPayment(ctx context.Context, in *RegisterPaymentRequest, opts ...grpc.CallOption) (*RegisterPaymentResponse, error)
ClaimPayment(ctx context.Context, in *ClaimPaymentRequest, opts ...grpc.CallOption) (*ClaimPaymentResponse, error)
SendTreeNonces(ctx context.Context, in *SendTreeNoncesRequest, opts ...grpc.CallOption) (*SendTreeNoncesResponse, error)
SendTreeSignatures(ctx context.Context, in *SendTreeSignaturesRequest, opts ...grpc.CallOption) (*SendTreeSignaturesResponse, error)
FinalizePayment(ctx context.Context, in *FinalizePaymentRequest, opts ...grpc.CallOption) (*FinalizePaymentResponse, error)
// TODO BTC: signTree rpc
GetRound(ctx context.Context, in *GetRoundRequest, opts ...grpc.CallOption) (*GetRoundResponse, error)
GetRoundById(ctx context.Context, in *GetRoundByIdRequest, opts ...grpc.CallOption) (*GetRoundByIdResponse, error)
GetEventStream(ctx context.Context, in *GetEventStreamRequest, opts ...grpc.CallOption) (ArkService_GetEventStreamClient, error)
@@ -59,6 +60,24 @@ func (c *arkServiceClient) ClaimPayment(ctx context.Context, in *ClaimPaymentReq
return out, nil
}
func (c *arkServiceClient) SendTreeNonces(ctx context.Context, in *SendTreeNoncesRequest, opts ...grpc.CallOption) (*SendTreeNoncesResponse, error) {
out := new(SendTreeNoncesResponse)
err := c.cc.Invoke(ctx, "/ark.v1.ArkService/SendTreeNonces", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *arkServiceClient) SendTreeSignatures(ctx context.Context, in *SendTreeSignaturesRequest, opts ...grpc.CallOption) (*SendTreeSignaturesResponse, error) {
out := new(SendTreeSignaturesResponse)
err := c.cc.Invoke(ctx, "/ark.v1.ArkService/SendTreeSignatures", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *arkServiceClient) FinalizePayment(ctx context.Context, in *FinalizePaymentRequest, opts ...grpc.CallOption) (*FinalizePaymentResponse, error) {
out := new(FinalizePaymentResponse)
err := c.cc.Invoke(ctx, "/ark.v1.ArkService/FinalizePayment", in, out, opts...)
@@ -178,8 +197,9 @@ func (c *arkServiceClient) CompletePayment(ctx context.Context, in *CompletePaym
type ArkServiceServer interface {
RegisterPayment(context.Context, *RegisterPaymentRequest) (*RegisterPaymentResponse, error)
ClaimPayment(context.Context, *ClaimPaymentRequest) (*ClaimPaymentResponse, error)
SendTreeNonces(context.Context, *SendTreeNoncesRequest) (*SendTreeNoncesResponse, error)
SendTreeSignatures(context.Context, *SendTreeSignaturesRequest) (*SendTreeSignaturesResponse, error)
FinalizePayment(context.Context, *FinalizePaymentRequest) (*FinalizePaymentResponse, error)
// TODO BTC: signTree rpc
GetRound(context.Context, *GetRoundRequest) (*GetRoundResponse, error)
GetRoundById(context.Context, *GetRoundByIdRequest) (*GetRoundByIdResponse, error)
GetEventStream(*GetEventStreamRequest, ArkService_GetEventStreamServer) error
@@ -201,6 +221,12 @@ func (UnimplementedArkServiceServer) RegisterPayment(context.Context, *RegisterP
func (UnimplementedArkServiceServer) ClaimPayment(context.Context, *ClaimPaymentRequest) (*ClaimPaymentResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ClaimPayment not implemented")
}
func (UnimplementedArkServiceServer) SendTreeNonces(context.Context, *SendTreeNoncesRequest) (*SendTreeNoncesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SendTreeNonces not implemented")
}
func (UnimplementedArkServiceServer) SendTreeSignatures(context.Context, *SendTreeSignaturesRequest) (*SendTreeSignaturesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SendTreeSignatures not implemented")
}
func (UnimplementedArkServiceServer) FinalizePayment(context.Context, *FinalizePaymentRequest) (*FinalizePaymentResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method FinalizePayment not implemented")
}
@@ -279,6 +305,42 @@ func _ArkService_ClaimPayment_Handler(srv interface{}, ctx context.Context, dec
return interceptor(ctx, in, info, handler)
}
func _ArkService_SendTreeNonces_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SendTreeNoncesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ArkServiceServer).SendTreeNonces(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ark.v1.ArkService/SendTreeNonces",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ArkServiceServer).SendTreeNonces(ctx, req.(*SendTreeNoncesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ArkService_SendTreeSignatures_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SendTreeSignaturesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ArkServiceServer).SendTreeSignatures(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ark.v1.ArkService/SendTreeSignatures",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ArkServiceServer).SendTreeSignatures(ctx, req.(*SendTreeSignaturesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ArkService_FinalizePayment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(FinalizePaymentRequest)
if err := dec(in); err != nil {
@@ -477,6 +539,14 @@ var ArkService_ServiceDesc = grpc.ServiceDesc{
MethodName: "ClaimPayment",
Handler: _ArkService_ClaimPayment_Handler,
},
{
MethodName: "SendTreeNonces",
Handler: _ArkService_SendTreeNonces_Handler,
},
{
MethodName: "SendTreeSignatures",
Handler: _ArkService_SendTreeSignatures_Handler,
},
{
MethodName: "FinalizePayment",
Handler: _ArkService_FinalizePayment_Handler,

View File

@@ -1,8 +1,11 @@
package covenantless
import (
"encoding/hex"
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
"github.com/ark-network/ark/client/utils"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/urfave/cli/v2"
)
@@ -69,8 +72,19 @@ func selfTransferAllPendingPayments(
return err
}
ephemeralKey, err := secp256k1.GeneratePrivateKey()
if err != nil {
return err
}
pubkey := hex.EncodeToString(ephemeralKey.PubKey().SerializeCompressed())
registerResponse, err := client.RegisterPayment(
ctx.Context, &arkv1.RegisterPaymentRequest{Inputs: inputs},
ctx.Context,
&arkv1.RegisterPaymentRequest{
Inputs: inputs,
EphemeralPubkey: &pubkey,
},
)
if err != nil {
return err
@@ -86,7 +100,7 @@ func selfTransferAllPendingPayments(
poolTxID, err := handleRoundStream(
ctx, client, registerResponse.GetId(),
pendingVtxos, secKey, receiversOutput,
pendingVtxos, secKey, receiversOutput, ephemeralKey,
)
if err != nil {
return err

View File

@@ -3,6 +3,7 @@ package covenantless
import (
"bytes"
"context"
"encoding/hex"
"fmt"
"io"
"strings"
@@ -14,6 +15,7 @@ import (
"github.com/ark-network/ark/common/tree"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil/psbt"
"github.com/btcsuite/btcd/txscript"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/urfave/cli/v2"
"google.golang.org/grpc"
@@ -220,12 +222,15 @@ func castCongestionTree(congestionTree tree.CongestionTree) *arkv1.Tree {
func handleRoundStream(
ctx *cli.Context, client arkv1.ArkServiceClient, paymentID string,
vtxosToSign []vtxo, secKey *secp256k1.PrivateKey, receivers []*arkv1.Output,
ephemeralKey *secp256k1.PrivateKey,
) (poolTxID string, err error) {
stream, err := client.GetEventStream(ctx.Context, &arkv1.GetEventStreamRequest{})
if err != nil {
return "", err
}
myEphemeralPublicKey := hex.EncodeToString(ephemeralKey.PubKey().SerializeCompressed())
var pingStop func()
pingReq := &arkv1.PingRequest{
PaymentId: paymentID,
@@ -236,6 +241,8 @@ func handleRoundStream(
defer pingStop()
var treeSignerSession bitcointree.SignerSession
for {
event, err := stream.Recv()
if err == io.EOF {
@@ -250,6 +257,143 @@ func handleRoundStream(
return "", fmt.Errorf("round failed: %s", e.GetReason())
}
if e := event.GetRoundSigning(); e != nil {
pingStop()
fmt.Println("tree created, generating nonces...")
cosignersPubKeysHex := e.GetCosignersPubkeys()
cosigners := make([]*secp256k1.PublicKey, 0, len(cosignersPubKeysHex))
for _, pubkeyHex := range cosignersPubKeysHex {
pubkeyBytes, err := hex.DecodeString(pubkeyHex)
if err != nil {
return "", err
}
pubkey, err := secp256k1.ParsePubKey(pubkeyBytes)
if err != nil {
return "", err
}
cosigners = append(cosigners, pubkey)
}
congestionTree, err := toCongestionTree(e.GetUnsignedTree())
if err != nil {
return "", err
}
minRelayFee, err := utils.GetMinRelayFee(ctx)
if err != nil {
return "", err
}
aspPubkey, err := utils.GetAspPublicKey(ctx)
if err != nil {
return "", err
}
lifetime, err := utils.GetRoundLifetime(ctx)
if err != nil {
return "", err
}
sweepClosure := bitcointree.CSVSigClosure{
Pubkey: aspPubkey,
Seconds: uint(lifetime),
}
sweepTapLeaf, err := sweepClosure.Leaf()
if err != nil {
return "", err
}
sweepTapTree := txscript.AssembleTaprootScriptTree(*sweepTapLeaf)
root := sweepTapTree.RootNode.TapHash()
treeSignerSession = bitcointree.NewTreeSignerSession(
ephemeralKey, congestionTree, int64(minRelayFee), root.CloneBytes(),
)
nonces, err := treeSignerSession.GetNonces()
if err != nil {
return "", err
}
if err := treeSignerSession.SetKeys(cosigners); err != nil {
return "", err
}
var nonceBuffer bytes.Buffer
if err := nonces.Encode(&nonceBuffer); err != nil {
return "", err
}
serializedNonces := hex.EncodeToString(nonceBuffer.Bytes())
if _, err := client.SendTreeNonces(ctx.Context, &arkv1.SendTreeNoncesRequest{
RoundId: e.GetId(),
PublicKey: myEphemeralPublicKey,
TreeNonces: serializedNonces,
}); err != nil {
return "", err
}
fmt.Println("nonces sent")
continue
}
if e := event.GetRoundSigningNoncesGenerated(); e != nil {
pingStop()
fmt.Println("nonces generated, signing the tree...")
if treeSignerSession == nil {
return "", fmt.Errorf("tree signer session not set")
}
combinedNoncesBytes, err := hex.DecodeString(e.GetTreeNonces())
if err != nil {
return "", err
}
combinedNonces, err := bitcointree.DecodeNonces(bytes.NewReader(combinedNoncesBytes))
if err != nil {
return "", err
}
if err := treeSignerSession.SetAggregatedNonces(combinedNonces); err != nil {
return "", err
}
sigs, err := treeSignerSession.Sign()
if err != nil {
return "", err
}
var sigBuffer bytes.Buffer
if err := sigs.Encode(&sigBuffer); err != nil {
return "", err
}
serializedSigs := hex.EncodeToString(sigBuffer.Bytes())
if _, err := client.SendTreeSignatures(ctx.Context, &arkv1.SendTreeSignaturesRequest{
RoundId: e.GetId(),
TreeSignatures: serializedSigs,
PublicKey: myEphemeralPublicKey,
}); err != nil {
return "", err
}
fmt.Println("tree signed")
continue
}
if e := event.GetRoundFinalization(); e != nil {
// stop pinging as soon as we receive some forfeit txs
pingStop()

View File

@@ -168,7 +168,11 @@ func (c *clArkBitcoinCLI) Onboard(ctx *cli.Context) error {
return err
}
if err := signer.SetKeys(cosigners, aggregatedNonces); err != nil {
if err := signer.SetKeys(cosigners); err != nil {
return err
}
if err := signer.SetAggregatedNonces(aggregatedNonces); err != nil {
return err
}

View File

@@ -2,6 +2,7 @@ package covenantless
import (
"bufio"
"encoding/hex"
"fmt"
"log"
"os"
@@ -11,6 +12,7 @@ import (
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
"github.com/ark-network/ark/client/utils"
"github.com/btcsuite/btcd/btcutil"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/urfave/cli/v2"
)
@@ -80,8 +82,16 @@ func collaborativeRedeem(
return err
}
ephemeralKey, err := secp256k1.GeneratePrivateKey()
if err != nil {
return err
}
pubkey := hex.EncodeToString(ephemeralKey.PubKey().SerializeCompressed())
registerResponse, err := client.RegisterPayment(ctx.Context, &arkv1.RegisterPaymentRequest{
Inputs: inputs,
EphemeralPubkey: &pubkey,
})
if err != nil {
return err
@@ -102,6 +112,7 @@ func collaborativeRedeem(
selectedCoins,
secKey,
receivers,
ephemeralKey,
)
if err != nil {
return err

View File

@@ -1,7 +1,9 @@
package bitcointree
import (
"bytes"
"errors"
"fmt"
"io"
"strings"
@@ -12,19 +14,44 @@ import (
"github.com/btcsuite/btcd/btcutil/psbt"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
)
var (
ErrCongestionTreeNotSet = errors.New("congestion tree not set")
ErrAggregateKeyNotSet = errors.New("aggregate key not set")
columnSeparator = byte('|')
rowSeparator = byte('/')
)
type TreeNonces [][][66]byte // public nonces
type Musig2Nonce struct {
PubNonce [66]byte
}
func (n *Musig2Nonce) Encode(w io.Writer) error {
_, err := w.Write(n.PubNonce[:])
return err
}
func (n *Musig2Nonce) Decode(r io.Reader) error {
bytes := make([]byte, 66)
_, err := r.Read(bytes)
if err != nil {
return err
}
n.PubNonce = [66]byte(bytes)
return nil
}
type TreeNonces [][]*Musig2Nonce // public nonces
type TreePartialSigs [][]*musig2.PartialSignature
type SignerSession interface {
GetNonces() (TreeNonces, error) // generate of return cached nonce for this session
SetKeys([]*btcec.PublicKey, TreeNonces) error // set the keys for this session (with the combined nonces)
GetNonces() (TreeNonces, error) // generate tree nonces for this session
SetKeys([]*btcec.PublicKey) error // set the cosigner public keys for this session
SetAggregatedNonces(TreeNonces) error // set the aggregated nonces
Sign() (TreePartialSigs, error) // sign the tree
}
@@ -36,6 +63,34 @@ type CoordinatorSession interface {
SignTree() (tree.CongestionTree, error)
}
func (n TreeNonces) Encode(w io.Writer) error {
matrix, err := encodeMatrix(n)
if err != nil {
return err
}
_, err = w.Write(matrix)
return err
}
func DecodeNonces(r io.Reader) (TreeNonces, error) {
return decodeMatrix(func() *Musig2Nonce { return new(Musig2Nonce) }, r)
}
func (s TreePartialSigs) Encode(w io.Writer) error {
matrix, err := encodeMatrix(s)
if err != nil {
return err
}
_, err = w.Write(matrix)
return err
}
func DecodeSignatures(r io.Reader) (TreePartialSigs, error) {
return decodeMatrix(func() *musig2.PartialSignature { return new(musig2.PartialSignature) }, r)
}
func AggregateKeys(
pubkeys []*btcec.PublicKey,
scriptRoot []byte,
@@ -100,62 +155,6 @@ func ValidateTreeSigs(
return nil
}
func (n TreeNonces) Decode(r io.Reader, matrixFormat []int) error {
for i := 0; i < len(matrixFormat); i++ {
for j := 0; j < matrixFormat[i]; j++ {
// read 66 bytes
nonce := make([]byte, 66)
_, err := r.Read(nonce)
if err != nil {
return err
}
n[i][j] = [66]byte(nonce)
}
}
return nil
}
func (n TreeNonces) Encode(w io.Writer) error {
for i := 0; i < len(n); i++ {
for j := 0; j < len(n[i]); j++ {
nonce := n[i][j][:]
_, err := w.Write(nonce)
if err != nil {
return err
}
}
}
return nil
}
func (n TreePartialSigs) Decode(r io.Reader, matrixFormat []int) error {
for i := 0; i < len(matrixFormat); i++ {
for j := 0; j < matrixFormat[i]; j++ {
sig := &musig2.PartialSignature{}
if err := sig.Decode(r); err != nil {
return err
}
}
}
return nil
}
func (n TreePartialSigs) Encode(w io.Writer) error {
for i := 0; i < len(n); i++ {
for j := 0; j < len(n[i]); j++ {
if err := n[i][j].Encode(w); err != nil {
return err
}
}
}
return nil
}
func NewTreeSignerSession(
signer *btcec.PrivateKey,
congestionTree tree.CongestionTree,
@@ -221,9 +220,9 @@ func (t *treeSignerSession) GetNonces() (TreeNonces, error) {
nonces := make(TreeNonces, 0)
for _, level := range t.myNonces {
levelNonces := make([][66]byte, 0)
levelNonces := make([]*Musig2Nonce, 0)
for _, nonce := range level {
levelNonces = append(levelNonces, nonce.PubNonce)
levelNonces = append(levelNonces, &Musig2Nonce{nonce.PubNonce})
}
nonces = append(nonces, levelNonces)
}
@@ -231,15 +230,11 @@ func (t *treeSignerSession) GetNonces() (TreeNonces, error) {
return nonces, nil
}
func (t *treeSignerSession) SetKeys(keys []*btcec.PublicKey, nonces TreeNonces) error {
func (t *treeSignerSession) SetKeys(keys []*btcec.PublicKey) error {
if t.keys != nil {
return errors.New("keys already set")
}
if t.aggregateNonces != nil {
return errors.New("nonces already set")
}
aggregateKey, err := AggregateKeys(keys, t.scriptRoot)
if err != nil {
return err
@@ -251,12 +246,20 @@ func (t *treeSignerSession) SetKeys(keys []*btcec.PublicKey, nonces TreeNonces)
}
t.prevoutFetcher = prevoutFetcher
t.aggregateNonces = nonces
t.keys = keys
return nil
}
func (t *treeSignerSession) SetAggregatedNonces(nonces TreeNonces) error {
if t.aggregateNonces != nil {
return errors.New("nonces already set")
}
t.aggregateNonces = nonces
return nil
}
func (t *treeSignerSession) Sign() (TreePartialSigs, error) {
if t.tree == nil {
return nil, ErrCongestionTreeNotSet
@@ -313,7 +316,7 @@ func (t *treeSignerSession) signPartial(partialTx *psbt.Packet, posx int, posy i
}
return musig2.Sign(
myNonce.SecNonce, seckey, aggregatedNonce, t.keys, [32]byte(message),
myNonce.SecNonce, seckey, aggregatedNonce.PubNonce, t.keys, [32]byte(message),
musig2.WithSortedKeys(), musig2.WithTaprootSignTweak(t.scriptRoot),
)
}
@@ -390,12 +393,11 @@ func (t *treeCoordinatorSession) AggregateNonces() (TreeNonces, error) {
aggregatedNonces := make(TreeNonces, 0)
for i, level := range t.tree {
levelNonces := make([][66]byte, 0)
levelNonces := make([]*Musig2Nonce, 0)
for j := range level {
nonces := make([][66]byte, 0)
for _, n := range t.nonces {
nonces = append(nonces, n[i][j])
nonces = append(nonces, n[i][j].PubNonce)
}
aggregatedNonce, err := musig2.AggregateNonces(nonces)
@@ -403,7 +405,7 @@ func (t *treeCoordinatorSession) AggregateNonces() (TreeNonces, error) {
return nil, err
}
levelNonces = append(levelNonces, aggregatedNonce)
levelNonces = append(levelNonces, &Musig2Nonce{aggregatedNonce})
}
aggregatedNonces = append(aggregatedNonces, levelNonces)
@@ -414,12 +416,17 @@ func (t *treeCoordinatorSession) AggregateNonces() (TreeNonces, error) {
// SignTree implements CoordinatorSession.
func (t *treeCoordinatorSession) SignTree() (tree.CongestionTree, error) {
var missingSigs int
for _, sig := range t.sigs {
if sig == nil {
return nil, errors.New("signatures not set")
missingSigs++
}
}
if missingSigs > 0 {
return nil, fmt.Errorf("missing %d signature(s)", missingSigs)
}
aggregatedKey, err := AggregateKeys(t.keys, t.scriptRoot)
if err != nil {
return nil, err
@@ -432,9 +439,18 @@ func (t *treeCoordinatorSession) SignTree() (tree.CongestionTree, error) {
return nil, err
}
var combinedNonce *secp256k1.PublicKey
sigs := make([]*musig2.PartialSignature, 0)
for _, sig := range t.sigs {
sigs = append(sigs, sig[i][j])
s := sig[i][j]
if s.R != nil {
combinedNonce = s.R
}
sigs = append(sigs, s)
}
if combinedNonce == nil {
return nil, errors.New("missing combined nonce")
}
inputFetcher := t.prevoutFetcher(partialTx)
@@ -448,7 +464,7 @@ func (t *treeCoordinatorSession) SignTree() (tree.CongestionTree, error) {
)
combinedSig := musig2.CombineSigs(
sigs[0].R, sigs,
combinedNonce, sigs,
musig2.WithTaprootTweakedCombine([32]byte(message), t.keys, t.scriptRoot, true),
)
if err != nil {
@@ -507,3 +523,71 @@ type treePrevOutFetcher struct {
func (f *treePrevOutFetcher) FetchPrevOutput(wire.OutPoint) *wire.TxOut {
return f.prevout
}
type writable interface {
Encode(w io.Writer) error
}
type readable interface {
Decode(r io.Reader) error
}
// encodeMatrix encode a matrix of serializable objects into a byte stream
func encodeMatrix[T writable](matrix [][]T) ([]byte, error) {
var buf bytes.Buffer
for _, row := range matrix {
for _, cell := range row {
if err := buf.WriteByte(columnSeparator); err != nil {
return nil, err
}
if err := cell.Encode(&buf); err != nil {
return nil, err
}
}
if err := buf.WriteByte(rowSeparator); err != nil {
return nil, err
}
}
return buf.Bytes(), nil
}
// decodeMatrix decode a byte stream into a matrix of serializable objects
func decodeMatrix[T readable](factory func() T, data io.Reader) ([][]T, error) {
matrix := make([][]T, 0)
row := make([]T, 0)
for {
separator := make([]byte, 1)
if _, err := data.Read(separator); err != nil {
if err == io.EOF {
break
}
return nil, err
}
b := separator[0]
if b == rowSeparator {
matrix = append(matrix, row)
row = make([]T, 0)
continue
}
cell := factory()
if err := cell.Decode(data); err != nil {
if err == io.EOF {
break
}
return nil, err
}
row = append(row, cell)
}
return matrix, nil
}

View File

@@ -1,7 +1,9 @@
package bitcointree_test
import (
"bytes"
"encoding/json"
"fmt"
"os"
"testing"
@@ -85,6 +87,31 @@ func TestRoundTripSignTree(t *testing.T) {
aspNonces, err := aspSession.GetNonces()
require.NoError(t, err)
s := bytes.NewBuffer(nil)
err = aspNonces[0][0].Encode(s)
require.NoError(t, err)
bitcointreeNonce := new(bitcointree.Musig2Nonce)
err = bitcointreeNonce.Decode(s)
require.NoError(t, err)
require.Equal(t, aspNonces[0][0], bitcointreeNonce)
var serializedNonces bytes.Buffer
err = aspNonces.Encode(&serializedNonces)
require.NoError(t, err)
decodedNonces, err := bitcointree.DecodeNonces(&serializedNonces)
require.NoError(t, err)
for i, nonces := range aspNonces {
for j, nonce := range nonces {
require.Equal(t, nonce.PubNonce, decodedNonces[i][j].PubNonce, fmt.Sprintf("matrix nonce not equal at index i: %d, j: %d", i, j))
}
}
err = aspCoordinator.AddNonce(alice.PubKey(), aliceNonces)
require.NoError(t, err)
@@ -101,18 +128,30 @@ func TestRoundTripSignTree(t *testing.T) {
err = aliceSession.SetKeys(
cosigners,
)
require.NoError(t, err)
err = aliceSession.SetAggregatedNonces(
aggregatedNonce,
)
require.NoError(t, err)
err = bobSession.SetKeys(
cosigners,
)
require.NoError(t, err)
err = bobSession.SetAggregatedNonces(
aggregatedNonce,
)
require.NoError(t, err)
err = aspSession.SetKeys(
cosigners,
)
require.NoError(t, err)
err = aspSession.SetAggregatedNonces(
aggregatedNonce,
)
require.NoError(t, err)
@@ -126,6 +165,22 @@ func TestRoundTripSignTree(t *testing.T) {
aspSig, err := aspSession.Sign()
require.NoError(t, err)
// check that the sigs are serializable
serializedSigs := bytes.NewBuffer(nil)
err = aspSig.Encode(serializedSigs)
require.NoError(t, err)
decodedSigs, err := bitcointree.DecodeSignatures(serializedSigs)
require.NoError(t, err)
for i, sigs := range aspSig {
for j, sig := range sigs {
require.Equal(t, sig.S, decodedSigs[i][j].S, fmt.Sprintf("matrix sig not equal at index i: %d, j: %d", i, j))
}
}
// coordinator receives the signatures and combines them
err = aspCoordinator.AddSig(alice.PubKey(), aliceSig)
require.NoError(t, err)

View File

@@ -3,7 +3,7 @@
## genrest: compiles rest client from stub with https://github.com/go-swagger/go-swagger
genrest:
@echo "Generating rest client from stub..."
@swagger generate client -f ../../server/api-spec/openapi/swagger/ark/v1/service.swagger.json -t ./client/rest/service --client-package=arkservice
@swagger generate client -f ../../api-spec/openapi/swagger/ark/v1/service.swagger.json -t ./client/rest/service --client-package=arkservice
## test: runs unit tests
test:

View File

@@ -4,7 +4,9 @@ import (
"context"
"time"
"github.com/ark-network/ark/common/bitcointree"
"github.com/ark-network/ark/common/tree"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
)
const (
@@ -25,7 +27,7 @@ type ASPClient interface {
ctx context.Context, tx, userPubkey string, congestionTree tree.CongestionTree,
) error
RegisterPayment(
ctx context.Context, inputs []VtxoKey,
ctx context.Context, inputs []VtxoKey, ephemeralPublicKey string,
) (string, error)
ClaimPayment(
ctx context.Context, paymentID string, outputs []Output,
@@ -33,7 +35,7 @@ type ASPClient interface {
GetEventStream(
ctx context.Context, paymentID string,
) (<-chan RoundEventChannel, error)
Ping(ctx context.Context, paymentID string) (*RoundFinalizationEvent, error)
Ping(ctx context.Context, paymentID string) (RoundEvent, error)
FinalizePayment(
ctx context.Context, signedForfeitTxs []string,
) error
@@ -43,6 +45,12 @@ type ASPClient interface {
CompletePayment(
ctx context.Context, signedRedeemTx string, signedUnconditionalForfeitTxs []string,
) error
SendTreeNonces(
ctx context.Context, roundID, cosignerPubkey string, nonces bitcointree.TreeNonces,
) error
SendTreeSignatures(
ctx context.Context, roundID, cosignerPubkey string, signatures bitcointree.TreePartialSigs,
) error
Close()
}
@@ -139,3 +147,18 @@ type RoundFailedEvent struct {
}
func (e RoundFailedEvent) isRoundEvent() {}
type RoundSigningStartedEvent struct {
ID string
UnsignedTree tree.CongestionTree
CosignersPublicKeys []*secp256k1.PublicKey
}
func (e RoundSigningStartedEvent) isRoundEvent() {}
type RoundSigningNoncesGeneratedEvent struct {
ID string
Nonces bitcointree.TreeNonces
}
func (e RoundSigningNoncesGeneratedEvent) isRoundEvent() {}

View File

@@ -1,15 +1,19 @@
package grpcclient
import (
"bytes"
"context"
"encoding/hex"
"fmt"
"strings"
"time"
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
"github.com/ark-network/ark/common/bitcointree"
"github.com/ark-network/ark/common/tree"
"github.com/ark-network/ark/pkg/client-sdk/client"
"github.com/ark-network/ark/pkg/client-sdk/internal/utils"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
@@ -73,7 +77,13 @@ func (a *grpcClient) GetEventStream(
return
}
a.eventsCh <- client.RoundEventChannel{Event: event{resp}.toRoundEvent()}
ev, err := event{resp}.toRoundEvent()
if err != nil {
a.eventsCh <- client.RoundEventChannel{Err: err}
return
}
a.eventsCh <- client.RoundEventChannel{Event: ev}
}
}()
@@ -146,11 +156,15 @@ func (a *grpcClient) Onboard(
}
func (a *grpcClient) RegisterPayment(
ctx context.Context, inputs []client.VtxoKey,
ctx context.Context, inputs []client.VtxoKey, ephemeralPublicKey string,
) (string, error) {
req := &arkv1.RegisterPaymentRequest{
Inputs: ins(inputs).toProto(),
}
if len(ephemeralPublicKey) > 0 {
req.EphemeralPubkey = &ephemeralPublicKey
}
resp, err := a.svc.RegisterPayment(ctx, req)
if err != nil {
return "", err
@@ -171,7 +185,7 @@ func (a *grpcClient) ClaimPayment(
func (a *grpcClient) Ping(
ctx context.Context, paymentID string,
) (*client.RoundFinalizationEvent, error) {
) (client.RoundEvent, error) {
req := &arkv1.PingRequest{
PaymentId: paymentID,
}
@@ -179,14 +193,8 @@ func (a *grpcClient) Ping(
if err != nil {
return nil, err
}
event := resp.GetEvent()
return &client.RoundFinalizationEvent{
ID: event.GetId(),
Tx: event.GetPoolTx(),
ForfeitTxs: event.GetForfeitTxs(),
Tree: treeFromProto{event.GetCongestionTree()}.parse(),
Connectors: event.GetConnectors(),
}, nil
return event{resp}.toRoundEvent()
}
func (a *grpcClient) FinalizePayment(
@@ -252,6 +260,54 @@ func (a *grpcClient) GetRoundByID(
}, nil
}
func (a *grpcClient) SendTreeNonces(
ctx context.Context, roundID, cosignerPubkey string, nonces bitcointree.TreeNonces,
) error {
var nonceBuffer bytes.Buffer
if err := nonces.Encode(&nonceBuffer); err != nil {
return err
}
serializedNonces := hex.EncodeToString(nonceBuffer.Bytes())
req := &arkv1.SendTreeNoncesRequest{
RoundId: roundID,
PublicKey: cosignerPubkey,
TreeNonces: serializedNonces,
}
if _, err := a.svc.SendTreeNonces(ctx, req); err != nil {
return err
}
return nil
}
func (a *grpcClient) SendTreeSignatures(
ctx context.Context, roundID, cosignerPubkey string, signatures bitcointree.TreePartialSigs,
) error {
var sigsBuffer bytes.Buffer
if err := signatures.Encode(&sigsBuffer); err != nil {
return err
}
serializedSigs := hex.EncodeToString(sigsBuffer.Bytes())
req := &arkv1.SendTreeSignaturesRequest{
RoundId: roundID,
PublicKey: cosignerPubkey,
TreeSignatures: serializedSigs,
}
if _, err := a.svc.SendTreeSignatures(ctx, req); err != nil {
return err
}
return nil
}
type out client.Output
func (o out) toProto() *arkv1.Output {
@@ -271,16 +327,25 @@ func (o outs) toProto() []*arkv1.Output {
return list
}
type event struct {
*arkv1.GetEventStreamResponse
// wrapper for GetEventStreamResponse and PingResponse
type eventResponse interface {
GetRoundFailed() *arkv1.RoundFailed
GetRoundFinalization() *arkv1.RoundFinalizationEvent
GetRoundFinalized() *arkv1.RoundFinalizedEvent
GetRoundSigning() *arkv1.RoundSigningEvent
GetRoundSigningNoncesGenerated() *arkv1.RoundSigningNoncesGeneratedEvent
}
func (e event) toRoundEvent() client.RoundEvent {
type event struct {
eventResponse
}
func (e event) toRoundEvent() (client.RoundEvent, error) {
if ee := e.GetRoundFailed(); ee != nil {
return client.RoundFailedEvent{
ID: ee.GetId(),
Reason: ee.GetReason(),
}
}, nil
}
if ee := e.GetRoundFinalization(); ee != nil {
tree := treeFromProto{ee.GetCongestionTree()}.parse()
@@ -290,13 +355,49 @@ func (e event) toRoundEvent() client.RoundEvent {
ForfeitTxs: ee.GetForfeitTxs(),
Tree: tree,
Connectors: ee.GetConnectors(),
}, nil
}
}
ee := e.GetRoundFinalized()
if ee := e.GetRoundFinalized(); ee != nil {
return client.RoundFinalizedEvent{
ID: ee.GetId(),
Txid: ee.GetPoolTxid(),
}, nil
}
if ee := e.GetRoundSigning(); ee != nil {
pubkeys := make([]*secp256k1.PublicKey, 0, len(ee.GetCosignersPubkeys()))
for _, pubkey := range ee.GetCosignersPubkeys() {
p, err := hex.DecodeString(pubkey)
if err != nil {
return nil, err
}
pk, err := secp256k1.ParsePubKey(p)
if err != nil {
return nil, err
}
pubkeys = append(pubkeys, pk)
}
return client.RoundSigningStartedEvent{
ID: ee.GetId(),
UnsignedTree: treeFromProto{ee.GetUnsignedTree()}.parse(),
CosignersPublicKeys: pubkeys,
}, nil
}
if ee := e.GetRoundSigningNoncesGenerated(); ee != nil {
nonces, err := bitcointree.DecodeNonces(hex.NewDecoder(strings.NewReader(ee.GetTreeNonces())))
if err != nil {
return nil, err
}
return client.RoundSigningNoncesGeneratedEvent{
ID: ee.GetId(),
Nonces: nonces,
}, nil
}
return nil, fmt.Errorf("unknown event")
}
type vtxo struct {

View File

@@ -1,7 +1,9 @@
package restclient
import (
"bytes"
"context"
"encoding/hex"
"fmt"
"net/url"
"strconv"
@@ -9,16 +11,16 @@ import (
"time"
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
"github.com/ark-network/ark/common/bitcointree"
"github.com/ark-network/ark/common/tree"
"github.com/ark-network/ark/pkg/client-sdk/client"
"github.com/ark-network/ark/pkg/client-sdk/client/rest/service/arkservice"
"github.com/ark-network/ark/pkg/client-sdk/client/rest/service/arkservice/ark_service"
"github.com/ark-network/ark/pkg/client-sdk/client/rest/service/models"
"github.com/ark-network/ark/pkg/client-sdk/internal/utils"
"github.com/btcsuite/btcd/btcutil/psbt"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
httptransport "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
"github.com/vulpemventures/go-elements/psetv2"
)
type restClient struct {
@@ -71,39 +73,7 @@ func (a *restClient) GetEventStream(
if event != nil {
a.eventsCh <- client.RoundEventChannel{
Event: *event,
}
for {
roundID := event.ID
round, err := a.GetRoundByID(ctx, roundID)
if err != nil {
a.eventsCh <- client.RoundEventChannel{
Err: err,
}
return
}
if round.Stage == client.RoundStageFinalized {
a.eventsCh <- client.RoundEventChannel{
Event: client.RoundFinalizedEvent{
ID: roundID,
Txid: getTxid(round.Tx),
},
}
return
}
if round.Stage == client.RoundStageFailed {
a.eventsCh <- client.RoundEventChannel{
Event: client.RoundFailedEvent{
ID: roundID,
},
}
return
}
time.Sleep(1 * time.Second)
Event: event,
}
}
@@ -286,7 +256,7 @@ func (a *restClient) Onboard(
}
func (a *restClient) RegisterPayment(
ctx context.Context, inputs []client.VtxoKey,
ctx context.Context, inputs []client.VtxoKey, ephemeralPublicKey string,
) (string, error) {
ins := make([]*models.V1Input, 0, len(inputs))
for _, i := range inputs {
@@ -295,11 +265,15 @@ func (a *restClient) RegisterPayment(
Vout: int64(i.VOut),
})
}
body := models.V1RegisterPaymentRequest{
body := &models.V1RegisterPaymentRequest{
Inputs: ins,
}
if len(ephemeralPublicKey) > 0 {
body.EphemeralPubkey = ephemeralPublicKey
}
resp, err := a.svc.ArkServiceRegisterPayment(
ark_service.NewArkServiceRegisterPaymentParams().WithBody(&body),
ark_service.NewArkServiceRegisterPaymentParams().WithBody(body),
)
if err != nil {
return "", err
@@ -331,7 +305,7 @@ func (a *restClient) ClaimPayment(
func (a *restClient) Ping(
ctx context.Context, paymentID string,
) (*client.RoundFinalizationEvent, error) {
) (client.RoundEvent, error) {
r := ark_service.NewArkServicePingParams()
r.SetPaymentID(paymentID)
resp, err := a.svc.ArkServicePing(r)
@@ -339,18 +313,65 @@ func (a *restClient) Ping(
return nil, err
}
var event *client.RoundFinalizationEvent
if resp.Payload.Event != nil {
event = &client.RoundFinalizationEvent{
ID: resp.Payload.Event.ID,
Tx: resp.Payload.Event.PoolTx,
ForfeitTxs: resp.Payload.Event.ForfeitTxs,
Tree: treeFromProto{resp.Payload.Event.CongestionTree}.parse(),
Connectors: resp.Payload.Event.Connectors,
payload := resp.Payload
if e := payload.RoundFailed; e != nil {
return client.RoundFailedEvent{
ID: e.ID,
Reason: e.Reason,
}, nil
}
if e := payload.RoundFinalization; e != nil {
tree := treeFromProto{e.CongestionTree}.parse()
return client.RoundFinalizationEvent{
ID: e.ID,
Tx: e.PoolTx,
ForfeitTxs: e.ForfeitTxs,
Tree: tree,
Connectors: e.Connectors,
}, nil
}
return event, nil
if e := payload.RoundFinalized; e != nil {
return client.RoundFinalizedEvent{
ID: e.ID,
Txid: e.PoolTxid,
}, nil
}
if e := payload.RoundSigning; e != nil {
pubkeys := make([]*secp256k1.PublicKey, 0, len(e.CosignersPubkeys))
for _, pubkey := range e.CosignersPubkeys {
p, err := hex.DecodeString(pubkey)
if err != nil {
return nil, err
}
pk, err := secp256k1.ParsePubKey(p)
if err != nil {
return nil, err
}
pubkeys = append(pubkeys, pk)
}
return client.RoundSigningStartedEvent{
ID: e.ID,
UnsignedTree: treeFromProto{e.UnsignedTree}.parse(),
CosignersPublicKeys: pubkeys,
}, nil
}
if e := payload.RoundSigningNoncesGenerated; e != nil {
nonces, err := bitcointree.DecodeNonces(hex.NewDecoder(strings.NewReader(e.TreeNonces)))
if err != nil {
return nil, err
}
return client.RoundSigningNoncesGeneratedEvent{
ID: e.ID,
Nonces: nonces,
}, nil
}
return nil, nil
}
func (a *restClient) FinalizePayment(
@@ -454,6 +475,58 @@ func (a *restClient) GetRoundByID(
}, nil
}
func (a *restClient) SendTreeNonces(
ctx context.Context, roundID, cosignerPubkey string, nonces bitcointree.TreeNonces,
) error {
var nonceBuffer bytes.Buffer
if err := nonces.Encode(&nonceBuffer); err != nil {
return err
}
serializedNonces := hex.EncodeToString(nonceBuffer.Bytes())
body := &models.V1SendTreeNoncesRequest{
RoundID: roundID,
PublicKey: cosignerPubkey,
TreeNonces: serializedNonces,
}
if _, err := a.svc.ArkServiceSendTreeNonces(
ark_service.NewArkServiceSendTreeNoncesParams().WithBody(body),
); err != nil {
return err
}
return nil
}
func (a *restClient) SendTreeSignatures(
ctx context.Context, roundID, cosignerPubkey string, signatures bitcointree.TreePartialSigs,
) error {
var sigsBuffer bytes.Buffer
if err := signatures.Encode(&sigsBuffer); err != nil {
return err
}
serializedSigs := hex.EncodeToString(sigsBuffer.Bytes())
body := &models.V1SendTreeSignaturesRequest{
RoundID: roundID,
PublicKey: cosignerPubkey,
TreeSignatures: serializedSigs,
}
if _, err := a.svc.ArkServiceSendTreeSignatures(
ark_service.NewArkServiceSendTreeSignaturesParams().WithBody(body),
); err != nil {
return err
}
return nil
}
func newRestClient(
serviceURL string,
) (ark_service.ClientService, error) {
@@ -551,13 +624,3 @@ func (t treeToProto) parse() *models.V1Tree {
Levels: levels,
}
}
func getTxid(tx string) string {
if ptx, _ := psetv2.NewPsetFromBase64(tx); ptx != nil {
utx, _ := ptx.UnsignedTx()
return utx.TxHash().String()
}
ptx, _ := psbt.NewFromRawBytes(strings.NewReader(tx), true)
return ptx.UnsignedTx.TxID()
}

View File

@@ -78,6 +78,10 @@ type ClientService interface {
ArkServiceRegisterPayment(params *ArkServiceRegisterPaymentParams, opts ...ClientOption) (*ArkServiceRegisterPaymentOK, error)
ArkServiceSendTreeNonces(params *ArkServiceSendTreeNoncesParams, opts ...ClientOption) (*ArkServiceSendTreeNoncesOK, error)
ArkServiceSendTreeSignatures(params *ArkServiceSendTreeSignaturesParams, opts ...ClientOption) (*ArkServiceSendTreeSignaturesOK, error)
SetTransport(transport runtime.ClientTransport)
}
@@ -304,7 +308,7 @@ func (a *Client) ArkServiceGetInfo(params *ArkServiceGetInfoParams, opts ...Clie
}
/*
ArkServiceGetRound ts o d o b t c sign tree rpc
ArkServiceGetRound ark service get round API
*/
func (a *Client) ArkServiceGetRound(params *ArkServiceGetRoundParams, opts ...ClientOption) (*ArkServiceGetRoundOK, error) {
// TODO: Validate the params before sending
@@ -525,6 +529,80 @@ func (a *Client) ArkServiceRegisterPayment(params *ArkServiceRegisterPaymentPara
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
/*
ArkServiceSendTreeNonces ark service send tree nonces API
*/
func (a *Client) ArkServiceSendTreeNonces(params *ArkServiceSendTreeNoncesParams, opts ...ClientOption) (*ArkServiceSendTreeNoncesOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewArkServiceSendTreeNoncesParams()
}
op := &runtime.ClientOperation{
ID: "ArkService_SendTreeNonces",
Method: "POST",
PathPattern: "/v1/payment/tree/nonces",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &ArkServiceSendTreeNoncesReader{formats: a.formats},
Context: params.Context,
Client: params.HTTPClient,
}
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
success, ok := result.(*ArkServiceSendTreeNoncesOK)
if ok {
return success, nil
}
// unexpected success response
unexpectedSuccess := result.(*ArkServiceSendTreeNoncesDefault)
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
/*
ArkServiceSendTreeSignatures ark service send tree signatures API
*/
func (a *Client) ArkServiceSendTreeSignatures(params *ArkServiceSendTreeSignaturesParams, opts ...ClientOption) (*ArkServiceSendTreeSignaturesOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewArkServiceSendTreeSignaturesParams()
}
op := &runtime.ClientOperation{
ID: "ArkService_SendTreeSignatures",
Method: "POST",
PathPattern: "/v1/payment/tree/signatures",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &ArkServiceSendTreeSignaturesReader{formats: a.formats},
Context: params.Context,
Client: params.HTTPClient,
}
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
success, ok := result.(*ArkServiceSendTreeSignaturesOK)
if ok {
return success, nil
}
// unexpected success response
unexpectedSuccess := result.(*ArkServiceSendTreeSignaturesDefault)
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
// SetTransport changes the transport on the client
func (a *Client) SetTransport(transport runtime.ClientTransport) {
a.transport = transport

View File

@@ -0,0 +1,150 @@
// Code generated by go-swagger; DO NOT EDIT.
package ark_service
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
"time"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
"github.com/ark-network/ark/pkg/client-sdk/client/rest/service/models"
)
// NewArkServiceSendTreeNoncesParams creates a new ArkServiceSendTreeNoncesParams object,
// with the default timeout for this client.
//
// Default values are not hydrated, since defaults are normally applied by the API server side.
//
// To enforce default values in parameter, use SetDefaults or WithDefaults.
func NewArkServiceSendTreeNoncesParams() *ArkServiceSendTreeNoncesParams {
return &ArkServiceSendTreeNoncesParams{
timeout: cr.DefaultTimeout,
}
}
// NewArkServiceSendTreeNoncesParamsWithTimeout creates a new ArkServiceSendTreeNoncesParams object
// with the ability to set a timeout on a request.
func NewArkServiceSendTreeNoncesParamsWithTimeout(timeout time.Duration) *ArkServiceSendTreeNoncesParams {
return &ArkServiceSendTreeNoncesParams{
timeout: timeout,
}
}
// NewArkServiceSendTreeNoncesParamsWithContext creates a new ArkServiceSendTreeNoncesParams object
// with the ability to set a context for a request.
func NewArkServiceSendTreeNoncesParamsWithContext(ctx context.Context) *ArkServiceSendTreeNoncesParams {
return &ArkServiceSendTreeNoncesParams{
Context: ctx,
}
}
// NewArkServiceSendTreeNoncesParamsWithHTTPClient creates a new ArkServiceSendTreeNoncesParams object
// with the ability to set a custom HTTPClient for a request.
func NewArkServiceSendTreeNoncesParamsWithHTTPClient(client *http.Client) *ArkServiceSendTreeNoncesParams {
return &ArkServiceSendTreeNoncesParams{
HTTPClient: client,
}
}
/*
ArkServiceSendTreeNoncesParams contains all the parameters to send to the API endpoint
for the ark service send tree nonces operation.
Typically these are written to a http.Request.
*/
type ArkServiceSendTreeNoncesParams struct {
// Body.
Body *models.V1SendTreeNoncesRequest
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the ark service send tree nonces params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *ArkServiceSendTreeNoncesParams) WithDefaults() *ArkServiceSendTreeNoncesParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the ark service send tree nonces params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *ArkServiceSendTreeNoncesParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the ark service send tree nonces params
func (o *ArkServiceSendTreeNoncesParams) WithTimeout(timeout time.Duration) *ArkServiceSendTreeNoncesParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the ark service send tree nonces params
func (o *ArkServiceSendTreeNoncesParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the ark service send tree nonces params
func (o *ArkServiceSendTreeNoncesParams) WithContext(ctx context.Context) *ArkServiceSendTreeNoncesParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the ark service send tree nonces params
func (o *ArkServiceSendTreeNoncesParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the ark service send tree nonces params
func (o *ArkServiceSendTreeNoncesParams) WithHTTPClient(client *http.Client) *ArkServiceSendTreeNoncesParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the ark service send tree nonces params
func (o *ArkServiceSendTreeNoncesParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithBody adds the body to the ark service send tree nonces params
func (o *ArkServiceSendTreeNoncesParams) WithBody(body *models.V1SendTreeNoncesRequest) *ArkServiceSendTreeNoncesParams {
o.SetBody(body)
return o
}
// SetBody adds the body to the ark service send tree nonces params
func (o *ArkServiceSendTreeNoncesParams) SetBody(body *models.V1SendTreeNoncesRequest) {
o.Body = body
}
// WriteToRequest writes these params to a swagger request
func (o *ArkServiceSendTreeNoncesParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
if err := r.SetTimeout(o.timeout); err != nil {
return err
}
var res []error
if o.Body != nil {
if err := r.SetBodyParam(o.Body); err != nil {
return err
}
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View File

@@ -0,0 +1,185 @@
// Code generated by go-swagger; DO NOT EDIT.
package ark_service
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
"fmt"
"io"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
"github.com/ark-network/ark/pkg/client-sdk/client/rest/service/models"
)
// ArkServiceSendTreeNoncesReader is a Reader for the ArkServiceSendTreeNonces structure.
type ArkServiceSendTreeNoncesReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *ArkServiceSendTreeNoncesReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
switch response.Code() {
case 200:
result := NewArkServiceSendTreeNoncesOK()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return result, nil
default:
result := NewArkServiceSendTreeNoncesDefault(response.Code())
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
if response.Code()/100 == 2 {
return result, nil
}
return nil, result
}
}
// NewArkServiceSendTreeNoncesOK creates a ArkServiceSendTreeNoncesOK with default headers values
func NewArkServiceSendTreeNoncesOK() *ArkServiceSendTreeNoncesOK {
return &ArkServiceSendTreeNoncesOK{}
}
/*
ArkServiceSendTreeNoncesOK describes a response with status code 200, with default header values.
A successful response.
*/
type ArkServiceSendTreeNoncesOK struct {
Payload models.V1SendTreeNoncesResponse
}
// IsSuccess returns true when this ark service send tree nonces o k response has a 2xx status code
func (o *ArkServiceSendTreeNoncesOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this ark service send tree nonces o k response has a 3xx status code
func (o *ArkServiceSendTreeNoncesOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this ark service send tree nonces o k response has a 4xx status code
func (o *ArkServiceSendTreeNoncesOK) IsClientError() bool {
return false
}
// IsServerError returns true when this ark service send tree nonces o k response has a 5xx status code
func (o *ArkServiceSendTreeNoncesOK) IsServerError() bool {
return false
}
// IsCode returns true when this ark service send tree nonces o k response a status code equal to that given
func (o *ArkServiceSendTreeNoncesOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the ark service send tree nonces o k response
func (o *ArkServiceSendTreeNoncesOK) Code() int {
return 200
}
func (o *ArkServiceSendTreeNoncesOK) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[POST /v1/payment/tree/nonces][%d] arkServiceSendTreeNoncesOK %s", 200, payload)
}
func (o *ArkServiceSendTreeNoncesOK) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[POST /v1/payment/tree/nonces][%d] arkServiceSendTreeNoncesOK %s", 200, payload)
}
func (o *ArkServiceSendTreeNoncesOK) GetPayload() models.V1SendTreeNoncesResponse {
return o.Payload
}
func (o *ArkServiceSendTreeNoncesOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}
// NewArkServiceSendTreeNoncesDefault creates a ArkServiceSendTreeNoncesDefault with default headers values
func NewArkServiceSendTreeNoncesDefault(code int) *ArkServiceSendTreeNoncesDefault {
return &ArkServiceSendTreeNoncesDefault{
_statusCode: code,
}
}
/*
ArkServiceSendTreeNoncesDefault describes a response with status code -1, with default header values.
An unexpected error response.
*/
type ArkServiceSendTreeNoncesDefault struct {
_statusCode int
Payload *models.RPCStatus
}
// IsSuccess returns true when this ark service send tree nonces default response has a 2xx status code
func (o *ArkServiceSendTreeNoncesDefault) IsSuccess() bool {
return o._statusCode/100 == 2
}
// IsRedirect returns true when this ark service send tree nonces default response has a 3xx status code
func (o *ArkServiceSendTreeNoncesDefault) IsRedirect() bool {
return o._statusCode/100 == 3
}
// IsClientError returns true when this ark service send tree nonces default response has a 4xx status code
func (o *ArkServiceSendTreeNoncesDefault) IsClientError() bool {
return o._statusCode/100 == 4
}
// IsServerError returns true when this ark service send tree nonces default response has a 5xx status code
func (o *ArkServiceSendTreeNoncesDefault) IsServerError() bool {
return o._statusCode/100 == 5
}
// IsCode returns true when this ark service send tree nonces default response a status code equal to that given
func (o *ArkServiceSendTreeNoncesDefault) IsCode(code int) bool {
return o._statusCode == code
}
// Code gets the status code for the ark service send tree nonces default response
func (o *ArkServiceSendTreeNoncesDefault) Code() int {
return o._statusCode
}
func (o *ArkServiceSendTreeNoncesDefault) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[POST /v1/payment/tree/nonces][%d] ArkService_SendTreeNonces default %s", o._statusCode, payload)
}
func (o *ArkServiceSendTreeNoncesDefault) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[POST /v1/payment/tree/nonces][%d] ArkService_SendTreeNonces default %s", o._statusCode, payload)
}
func (o *ArkServiceSendTreeNoncesDefault) GetPayload() *models.RPCStatus {
return o.Payload
}
func (o *ArkServiceSendTreeNoncesDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
o.Payload = new(models.RPCStatus)
// response payload
if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}

View File

@@ -0,0 +1,150 @@
// Code generated by go-swagger; DO NOT EDIT.
package ark_service
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
"time"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
"github.com/ark-network/ark/pkg/client-sdk/client/rest/service/models"
)
// NewArkServiceSendTreeSignaturesParams creates a new ArkServiceSendTreeSignaturesParams object,
// with the default timeout for this client.
//
// Default values are not hydrated, since defaults are normally applied by the API server side.
//
// To enforce default values in parameter, use SetDefaults or WithDefaults.
func NewArkServiceSendTreeSignaturesParams() *ArkServiceSendTreeSignaturesParams {
return &ArkServiceSendTreeSignaturesParams{
timeout: cr.DefaultTimeout,
}
}
// NewArkServiceSendTreeSignaturesParamsWithTimeout creates a new ArkServiceSendTreeSignaturesParams object
// with the ability to set a timeout on a request.
func NewArkServiceSendTreeSignaturesParamsWithTimeout(timeout time.Duration) *ArkServiceSendTreeSignaturesParams {
return &ArkServiceSendTreeSignaturesParams{
timeout: timeout,
}
}
// NewArkServiceSendTreeSignaturesParamsWithContext creates a new ArkServiceSendTreeSignaturesParams object
// with the ability to set a context for a request.
func NewArkServiceSendTreeSignaturesParamsWithContext(ctx context.Context) *ArkServiceSendTreeSignaturesParams {
return &ArkServiceSendTreeSignaturesParams{
Context: ctx,
}
}
// NewArkServiceSendTreeSignaturesParamsWithHTTPClient creates a new ArkServiceSendTreeSignaturesParams object
// with the ability to set a custom HTTPClient for a request.
func NewArkServiceSendTreeSignaturesParamsWithHTTPClient(client *http.Client) *ArkServiceSendTreeSignaturesParams {
return &ArkServiceSendTreeSignaturesParams{
HTTPClient: client,
}
}
/*
ArkServiceSendTreeSignaturesParams contains all the parameters to send to the API endpoint
for the ark service send tree signatures operation.
Typically these are written to a http.Request.
*/
type ArkServiceSendTreeSignaturesParams struct {
// Body.
Body *models.V1SendTreeSignaturesRequest
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the ark service send tree signatures params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *ArkServiceSendTreeSignaturesParams) WithDefaults() *ArkServiceSendTreeSignaturesParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the ark service send tree signatures params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *ArkServiceSendTreeSignaturesParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the ark service send tree signatures params
func (o *ArkServiceSendTreeSignaturesParams) WithTimeout(timeout time.Duration) *ArkServiceSendTreeSignaturesParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the ark service send tree signatures params
func (o *ArkServiceSendTreeSignaturesParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the ark service send tree signatures params
func (o *ArkServiceSendTreeSignaturesParams) WithContext(ctx context.Context) *ArkServiceSendTreeSignaturesParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the ark service send tree signatures params
func (o *ArkServiceSendTreeSignaturesParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the ark service send tree signatures params
func (o *ArkServiceSendTreeSignaturesParams) WithHTTPClient(client *http.Client) *ArkServiceSendTreeSignaturesParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the ark service send tree signatures params
func (o *ArkServiceSendTreeSignaturesParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithBody adds the body to the ark service send tree signatures params
func (o *ArkServiceSendTreeSignaturesParams) WithBody(body *models.V1SendTreeSignaturesRequest) *ArkServiceSendTreeSignaturesParams {
o.SetBody(body)
return o
}
// SetBody adds the body to the ark service send tree signatures params
func (o *ArkServiceSendTreeSignaturesParams) SetBody(body *models.V1SendTreeSignaturesRequest) {
o.Body = body
}
// WriteToRequest writes these params to a swagger request
func (o *ArkServiceSendTreeSignaturesParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
if err := r.SetTimeout(o.timeout); err != nil {
return err
}
var res []error
if o.Body != nil {
if err := r.SetBodyParam(o.Body); err != nil {
return err
}
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View File

@@ -0,0 +1,185 @@
// Code generated by go-swagger; DO NOT EDIT.
package ark_service
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
"fmt"
"io"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
"github.com/ark-network/ark/pkg/client-sdk/client/rest/service/models"
)
// ArkServiceSendTreeSignaturesReader is a Reader for the ArkServiceSendTreeSignatures structure.
type ArkServiceSendTreeSignaturesReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *ArkServiceSendTreeSignaturesReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
switch response.Code() {
case 200:
result := NewArkServiceSendTreeSignaturesOK()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return result, nil
default:
result := NewArkServiceSendTreeSignaturesDefault(response.Code())
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
if response.Code()/100 == 2 {
return result, nil
}
return nil, result
}
}
// NewArkServiceSendTreeSignaturesOK creates a ArkServiceSendTreeSignaturesOK with default headers values
func NewArkServiceSendTreeSignaturesOK() *ArkServiceSendTreeSignaturesOK {
return &ArkServiceSendTreeSignaturesOK{}
}
/*
ArkServiceSendTreeSignaturesOK describes a response with status code 200, with default header values.
A successful response.
*/
type ArkServiceSendTreeSignaturesOK struct {
Payload models.V1SendTreeSignaturesResponse
}
// IsSuccess returns true when this ark service send tree signatures o k response has a 2xx status code
func (o *ArkServiceSendTreeSignaturesOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this ark service send tree signatures o k response has a 3xx status code
func (o *ArkServiceSendTreeSignaturesOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this ark service send tree signatures o k response has a 4xx status code
func (o *ArkServiceSendTreeSignaturesOK) IsClientError() bool {
return false
}
// IsServerError returns true when this ark service send tree signatures o k response has a 5xx status code
func (o *ArkServiceSendTreeSignaturesOK) IsServerError() bool {
return false
}
// IsCode returns true when this ark service send tree signatures o k response a status code equal to that given
func (o *ArkServiceSendTreeSignaturesOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the ark service send tree signatures o k response
func (o *ArkServiceSendTreeSignaturesOK) Code() int {
return 200
}
func (o *ArkServiceSendTreeSignaturesOK) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[POST /v1/payment/tree/signatures][%d] arkServiceSendTreeSignaturesOK %s", 200, payload)
}
func (o *ArkServiceSendTreeSignaturesOK) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[POST /v1/payment/tree/signatures][%d] arkServiceSendTreeSignaturesOK %s", 200, payload)
}
func (o *ArkServiceSendTreeSignaturesOK) GetPayload() models.V1SendTreeSignaturesResponse {
return o.Payload
}
func (o *ArkServiceSendTreeSignaturesOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}
// NewArkServiceSendTreeSignaturesDefault creates a ArkServiceSendTreeSignaturesDefault with default headers values
func NewArkServiceSendTreeSignaturesDefault(code int) *ArkServiceSendTreeSignaturesDefault {
return &ArkServiceSendTreeSignaturesDefault{
_statusCode: code,
}
}
/*
ArkServiceSendTreeSignaturesDefault describes a response with status code -1, with default header values.
An unexpected error response.
*/
type ArkServiceSendTreeSignaturesDefault struct {
_statusCode int
Payload *models.RPCStatus
}
// IsSuccess returns true when this ark service send tree signatures default response has a 2xx status code
func (o *ArkServiceSendTreeSignaturesDefault) IsSuccess() bool {
return o._statusCode/100 == 2
}
// IsRedirect returns true when this ark service send tree signatures default response has a 3xx status code
func (o *ArkServiceSendTreeSignaturesDefault) IsRedirect() bool {
return o._statusCode/100 == 3
}
// IsClientError returns true when this ark service send tree signatures default response has a 4xx status code
func (o *ArkServiceSendTreeSignaturesDefault) IsClientError() bool {
return o._statusCode/100 == 4
}
// IsServerError returns true when this ark service send tree signatures default response has a 5xx status code
func (o *ArkServiceSendTreeSignaturesDefault) IsServerError() bool {
return o._statusCode/100 == 5
}
// IsCode returns true when this ark service send tree signatures default response a status code equal to that given
func (o *ArkServiceSendTreeSignaturesDefault) IsCode(code int) bool {
return o._statusCode == code
}
// Code gets the status code for the ark service send tree signatures default response
func (o *ArkServiceSendTreeSignaturesDefault) Code() int {
return o._statusCode
}
func (o *ArkServiceSendTreeSignaturesDefault) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[POST /v1/payment/tree/signatures][%d] ArkService_SendTreeSignatures default %s", o._statusCode, payload)
}
func (o *ArkServiceSendTreeSignaturesDefault) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[POST /v1/payment/tree/signatures][%d] ArkService_SendTreeSignatures default %s", o._statusCode, payload)
}
func (o *ArkServiceSendTreeSignaturesDefault) GetPayload() *models.RPCStatus {
return o.Payload
}
func (o *ArkServiceSendTreeSignaturesDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
o.Payload = new(models.RPCStatus)
// response payload
if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}

View File

@@ -21,11 +21,17 @@ type V1GetEventStreamResponse struct {
// round failed
RoundFailed *V1RoundFailed `json:"roundFailed,omitempty"`
// TODO: BTC add "signTree" event
// round finalization
RoundFinalization *V1RoundFinalizationEvent `json:"roundFinalization,omitempty"`
// round finalized
RoundFinalized *V1RoundFinalizedEvent `json:"roundFinalized,omitempty"`
// round signing
RoundSigning *V1RoundSigningEvent `json:"roundSigning,omitempty"`
// round signing nonces generated
RoundSigningNoncesGenerated *V1RoundSigningNoncesGeneratedEvent `json:"roundSigningNoncesGenerated,omitempty"`
}
// Validate validates this v1 get event stream response
@@ -44,6 +50,14 @@ func (m *V1GetEventStreamResponse) Validate(formats strfmt.Registry) error {
res = append(res, err)
}
if err := m.validateRoundSigning(formats); err != nil {
res = append(res, err)
}
if err := m.validateRoundSigningNoncesGenerated(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
@@ -107,6 +121,44 @@ func (m *V1GetEventStreamResponse) validateRoundFinalized(formats strfmt.Registr
return nil
}
func (m *V1GetEventStreamResponse) validateRoundSigning(formats strfmt.Registry) error {
if swag.IsZero(m.RoundSigning) { // not required
return nil
}
if m.RoundSigning != nil {
if err := m.RoundSigning.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("roundSigning")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("roundSigning")
}
return err
}
}
return nil
}
func (m *V1GetEventStreamResponse) validateRoundSigningNoncesGenerated(formats strfmt.Registry) error {
if swag.IsZero(m.RoundSigningNoncesGenerated) { // not required
return nil
}
if m.RoundSigningNoncesGenerated != nil {
if err := m.RoundSigningNoncesGenerated.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("roundSigningNoncesGenerated")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("roundSigningNoncesGenerated")
}
return err
}
}
return nil
}
// ContextValidate validate this v1 get event stream response based on the context it is used
func (m *V1GetEventStreamResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
@@ -123,6 +175,14 @@ func (m *V1GetEventStreamResponse) ContextValidate(ctx context.Context, formats
res = append(res, err)
}
if err := m.contextValidateRoundSigning(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateRoundSigningNoncesGenerated(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
@@ -192,6 +252,48 @@ func (m *V1GetEventStreamResponse) contextValidateRoundFinalized(ctx context.Con
return nil
}
func (m *V1GetEventStreamResponse) contextValidateRoundSigning(ctx context.Context, formats strfmt.Registry) error {
if m.RoundSigning != nil {
if swag.IsZero(m.RoundSigning) { // not required
return nil
}
if err := m.RoundSigning.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("roundSigning")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("roundSigning")
}
return err
}
}
return nil
}
func (m *V1GetEventStreamResponse) contextValidateRoundSigningNoncesGenerated(ctx context.Context, formats strfmt.Registry) error {
if m.RoundSigningNoncesGenerated != nil {
if swag.IsZero(m.RoundSigningNoncesGenerated) { // not required
return nil
}
if err := m.RoundSigningNoncesGenerated.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("roundSigningNoncesGenerated")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("roundSigningNoncesGenerated")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *V1GetEventStreamResponse) MarshalBinary() ([]byte, error) {
if m == nil {

View File

@@ -18,18 +18,43 @@ import (
// swagger:model v1PingResponse
type V1PingResponse struct {
// event
Event *V1RoundFinalizationEvent `json:"event,omitempty"`
// round failed
RoundFailed *V1RoundFailed `json:"roundFailed,omitempty"`
// forfeit txs
ForfeitTxs []string `json:"forfeitTxs"`
// round finalization
RoundFinalization *V1RoundFinalizationEvent `json:"roundFinalization,omitempty"`
// round finalized
RoundFinalized *V1RoundFinalizedEvent `json:"roundFinalized,omitempty"`
// round signing
RoundSigning *V1RoundSigningEvent `json:"roundSigning,omitempty"`
// round signing nonces generated
RoundSigningNoncesGenerated *V1RoundSigningNoncesGeneratedEvent `json:"roundSigningNoncesGenerated,omitempty"`
}
// Validate validates this v1 ping response
func (m *V1PingResponse) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateEvent(formats); err != nil {
if err := m.validateRoundFailed(formats); err != nil {
res = append(res, err)
}
if err := m.validateRoundFinalization(formats); err != nil {
res = append(res, err)
}
if err := m.validateRoundFinalized(formats); err != nil {
res = append(res, err)
}
if err := m.validateRoundSigning(formats); err != nil {
res = append(res, err)
}
if err := m.validateRoundSigningNoncesGenerated(formats); err != nil {
res = append(res, err)
}
@@ -39,17 +64,93 @@ func (m *V1PingResponse) Validate(formats strfmt.Registry) error {
return nil
}
func (m *V1PingResponse) validateEvent(formats strfmt.Registry) error {
if swag.IsZero(m.Event) { // not required
func (m *V1PingResponse) validateRoundFailed(formats strfmt.Registry) error {
if swag.IsZero(m.RoundFailed) { // not required
return nil
}
if m.Event != nil {
if err := m.Event.Validate(formats); err != nil {
if m.RoundFailed != nil {
if err := m.RoundFailed.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("event")
return ve.ValidateName("roundFailed")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("event")
return ce.ValidateName("roundFailed")
}
return err
}
}
return nil
}
func (m *V1PingResponse) validateRoundFinalization(formats strfmt.Registry) error {
if swag.IsZero(m.RoundFinalization) { // not required
return nil
}
if m.RoundFinalization != nil {
if err := m.RoundFinalization.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("roundFinalization")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("roundFinalization")
}
return err
}
}
return nil
}
func (m *V1PingResponse) validateRoundFinalized(formats strfmt.Registry) error {
if swag.IsZero(m.RoundFinalized) { // not required
return nil
}
if m.RoundFinalized != nil {
if err := m.RoundFinalized.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("roundFinalized")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("roundFinalized")
}
return err
}
}
return nil
}
func (m *V1PingResponse) validateRoundSigning(formats strfmt.Registry) error {
if swag.IsZero(m.RoundSigning) { // not required
return nil
}
if m.RoundSigning != nil {
if err := m.RoundSigning.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("roundSigning")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("roundSigning")
}
return err
}
}
return nil
}
func (m *V1PingResponse) validateRoundSigningNoncesGenerated(formats strfmt.Registry) error {
if swag.IsZero(m.RoundSigningNoncesGenerated) { // not required
return nil
}
if m.RoundSigningNoncesGenerated != nil {
if err := m.RoundSigningNoncesGenerated.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("roundSigningNoncesGenerated")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("roundSigningNoncesGenerated")
}
return err
}
@@ -62,7 +163,23 @@ func (m *V1PingResponse) validateEvent(formats strfmt.Registry) error {
func (m *V1PingResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateEvent(ctx, formats); err != nil {
if err := m.contextValidateRoundFailed(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateRoundFinalization(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateRoundFinalized(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateRoundSigning(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateRoundSigningNoncesGenerated(ctx, formats); err != nil {
res = append(res, err)
}
@@ -72,19 +189,103 @@ func (m *V1PingResponse) ContextValidate(ctx context.Context, formats strfmt.Reg
return nil
}
func (m *V1PingResponse) contextValidateEvent(ctx context.Context, formats strfmt.Registry) error {
func (m *V1PingResponse) contextValidateRoundFailed(ctx context.Context, formats strfmt.Registry) error {
if m.Event != nil {
if m.RoundFailed != nil {
if swag.IsZero(m.Event) { // not required
if swag.IsZero(m.RoundFailed) { // not required
return nil
}
if err := m.Event.ContextValidate(ctx, formats); err != nil {
if err := m.RoundFailed.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("event")
return ve.ValidateName("roundFailed")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("event")
return ce.ValidateName("roundFailed")
}
return err
}
}
return nil
}
func (m *V1PingResponse) contextValidateRoundFinalization(ctx context.Context, formats strfmt.Registry) error {
if m.RoundFinalization != nil {
if swag.IsZero(m.RoundFinalization) { // not required
return nil
}
if err := m.RoundFinalization.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("roundFinalization")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("roundFinalization")
}
return err
}
}
return nil
}
func (m *V1PingResponse) contextValidateRoundFinalized(ctx context.Context, formats strfmt.Registry) error {
if m.RoundFinalized != nil {
if swag.IsZero(m.RoundFinalized) { // not required
return nil
}
if err := m.RoundFinalized.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("roundFinalized")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("roundFinalized")
}
return err
}
}
return nil
}
func (m *V1PingResponse) contextValidateRoundSigning(ctx context.Context, formats strfmt.Registry) error {
if m.RoundSigning != nil {
if swag.IsZero(m.RoundSigning) { // not required
return nil
}
if err := m.RoundSigning.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("roundSigning")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("roundSigning")
}
return err
}
}
return nil
}
func (m *V1PingResponse) contextValidateRoundSigningNoncesGenerated(ctx context.Context, formats strfmt.Registry) error {
if m.RoundSigningNoncesGenerated != nil {
if swag.IsZero(m.RoundSigningNoncesGenerated) { // not required
return nil
}
if err := m.RoundSigningNoncesGenerated.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("roundSigningNoncesGenerated")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("roundSigningNoncesGenerated")
}
return err
}

View File

@@ -19,6 +19,9 @@ import (
// swagger:model v1RegisterPaymentRequest
type V1RegisterPaymentRequest struct {
// ephemeral pubkey
EphemeralPubkey string `json:"ephemeralPubkey,omitempty"`
// inputs
Inputs []*V1Input `json:"inputs"`
}

View File

@@ -0,0 +1,115 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// V1RoundSigningEvent v1 round signing event
//
// swagger:model v1RoundSigningEvent
type V1RoundSigningEvent struct {
// cosigners pubkeys
CosignersPubkeys []string `json:"cosignersPubkeys"`
// id
ID string `json:"id,omitempty"`
// unsigned tree
UnsignedTree *V1Tree `json:"unsignedTree,omitempty"`
}
// Validate validates this v1 round signing event
func (m *V1RoundSigningEvent) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateUnsignedTree(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *V1RoundSigningEvent) validateUnsignedTree(formats strfmt.Registry) error {
if swag.IsZero(m.UnsignedTree) { // not required
return nil
}
if m.UnsignedTree != nil {
if err := m.UnsignedTree.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("unsignedTree")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("unsignedTree")
}
return err
}
}
return nil
}
// ContextValidate validate this v1 round signing event based on the context it is used
func (m *V1RoundSigningEvent) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateUnsignedTree(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *V1RoundSigningEvent) contextValidateUnsignedTree(ctx context.Context, formats strfmt.Registry) error {
if m.UnsignedTree != nil {
if swag.IsZero(m.UnsignedTree) { // not required
return nil
}
if err := m.UnsignedTree.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("unsignedTree")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("unsignedTree")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *V1RoundSigningEvent) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *V1RoundSigningEvent) UnmarshalBinary(b []byte) error {
var res V1RoundSigningEvent
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,53 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// V1RoundSigningNoncesGeneratedEvent v1 round signing nonces generated event
//
// swagger:model v1RoundSigningNoncesGeneratedEvent
type V1RoundSigningNoncesGeneratedEvent struct {
// id
ID string `json:"id,omitempty"`
// tree nonces
TreeNonces string `json:"treeNonces,omitempty"`
}
// Validate validates this v1 round signing nonces generated event
func (m *V1RoundSigningNoncesGeneratedEvent) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this v1 round signing nonces generated event based on context it is used
func (m *V1RoundSigningNoncesGeneratedEvent) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *V1RoundSigningNoncesGeneratedEvent) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *V1RoundSigningNoncesGeneratedEvent) UnmarshalBinary(b []byte) error {
var res V1RoundSigningNoncesGeneratedEvent
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,56 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// V1SendTreeNoncesRequest v1 send tree nonces request
//
// swagger:model v1SendTreeNoncesRequest
type V1SendTreeNoncesRequest struct {
// public key
PublicKey string `json:"publicKey,omitempty"`
// round Id
RoundID string `json:"roundId,omitempty"`
// tree nonces
TreeNonces string `json:"treeNonces,omitempty"`
}
// Validate validates this v1 send tree nonces request
func (m *V1SendTreeNoncesRequest) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this v1 send tree nonces request based on context it is used
func (m *V1SendTreeNoncesRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *V1SendTreeNoncesRequest) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *V1SendTreeNoncesRequest) UnmarshalBinary(b []byte) error {
var res V1SendTreeNoncesRequest
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,11 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
// V1SendTreeNoncesResponse v1 send tree nonces response
//
// swagger:model v1SendTreeNoncesResponse
type V1SendTreeNoncesResponse interface{}

View File

@@ -0,0 +1,56 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// V1SendTreeSignaturesRequest v1 send tree signatures request
//
// swagger:model v1SendTreeSignaturesRequest
type V1SendTreeSignaturesRequest struct {
// public key
PublicKey string `json:"publicKey,omitempty"`
// round Id
RoundID string `json:"roundId,omitempty"`
// tree signatures
TreeSignatures string `json:"treeSignatures,omitempty"`
}
// Validate validates this v1 send tree signatures request
func (m *V1SendTreeSignaturesRequest) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this v1 send tree signatures request based on context it is used
func (m *V1SendTreeSignaturesRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *V1SendTreeSignaturesRequest) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *V1SendTreeSignaturesRequest) UnmarshalBinary(b []byte) error {
var res V1SendTreeSignaturesRequest
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,11 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
// V1SendTreeSignaturesResponse v1 send tree signatures response
//
// swagger:model v1SendTreeSignaturesResponse
type V1SendTreeSignaturesResponse interface{}

View File

@@ -528,7 +528,7 @@ func (a *covenantArkClient) CollaborativeRedeem(
})
}
paymentID, err := a.client.RegisterPayment(ctx, inputs)
paymentID, err := a.client.RegisterPayment(ctx, inputs, "") // ephemeralPublicKey is not required for covenant
if err != nil {
return "", err
}
@@ -796,7 +796,7 @@ func (a *covenantArkClient) sendOffchain(
}
paymentID, err := a.client.RegisterPayment(
ctx, inputs,
ctx, inputs, "", // ephemeralPublicKey is not required for covenant
)
if err != nil {
return "", err

View File

@@ -262,7 +262,11 @@ func (a *covenantlessArkClient) Onboard(
return "", err
}
if err := signer.SetKeys(cosigners, aggregatedNonces); err != nil {
if err := signer.SetKeys(cosigners); err != nil {
return "", err
}
if err := signer.SetAggregatedNonces(aggregatedNonces); err != nil {
return "", err
}
@@ -608,7 +612,16 @@ func (a *covenantlessArkClient) CollaborativeRedeem(
})
}
paymentID, err := a.client.RegisterPayment(ctx, inputs)
roundEphemeralKey, err := secp256k1.GeneratePrivateKey()
if err != nil {
return "", err
}
paymentID, err := a.client.RegisterPayment(
ctx,
inputs,
hex.EncodeToString(roundEphemeralKey.PubKey().SerializeCompressed()),
)
if err != nil {
return "", err
}
@@ -618,7 +631,7 @@ func (a *covenantlessArkClient) CollaborativeRedeem(
}
poolTxID, err := a.handleRoundStream(
ctx, paymentID, selectedCoins, receivers,
ctx, paymentID, selectedCoins, receivers, roundEphemeralKey,
)
if err != nil {
return "", err
@@ -990,8 +1003,13 @@ func (a *covenantlessArkClient) sendOffchain(
})
}
roundEphemeralKey, err := secp256k1.GeneratePrivateKey()
if err != nil {
return "", err
}
paymentID, err := a.client.RegisterPayment(
ctx, inputs,
ctx, inputs, hex.EncodeToString(roundEphemeralKey.PubKey().SerializeCompressed()),
)
if err != nil {
return "", err
@@ -1006,7 +1024,7 @@ func (a *covenantlessArkClient) sendOffchain(
log.Infof("payment registered with id: %s", paymentID)
poolTxID, err := a.handleRoundStream(
ctx, paymentID, selectedCoins, receiversOutput,
ctx, paymentID, selectedCoins, receiversOutput, roundEphemeralKey,
)
if err != nil {
return "", err
@@ -1144,7 +1162,10 @@ func (a *covenantlessArkClient) addVtxoInput(
func (a *covenantlessArkClient) handleRoundStream(
ctx context.Context,
paymentID string, vtxosToSign []client.Vtxo, receivers []client.Output,
paymentID string,
vtxosToSign []client.Vtxo,
receivers []client.Output,
roundEphemeralKey *secp256k1.PrivateKey,
) (string, error) {
eventsCh, err := a.client.GetEventStream(ctx, paymentID)
if err != nil {
@@ -1158,6 +1179,8 @@ func (a *covenantlessArkClient) handleRoundStream(
defer pingStop()
var signerSession bitcointree.SignerSession
for {
select {
case <-ctx.Done():
@@ -1172,6 +1195,25 @@ func (a *covenantlessArkClient) handleRoundStream(
return event.(client.RoundFinalizedEvent).Txid, nil
case client.RoundFailedEvent:
return "", fmt.Errorf("round failed: %s", event.(client.RoundFailedEvent).Reason)
case client.RoundSigningStartedEvent:
pingStop()
log.Info("a round signing started")
signerSession, err = a.handleRoundSigningStarted(
ctx, roundEphemeralKey, event.(client.RoundSigningStartedEvent),
)
if err != nil {
return "", err
}
continue
case client.RoundSigningNoncesGeneratedEvent:
pingStop()
log.Info("round combined nonces generated")
if err := a.handleRoundSigningNoncesGenerated(
ctx, event.(client.RoundSigningNoncesGeneratedEvent), roundEphemeralKey, signerSession,
); err != nil {
return "", err
}
continue
case client.RoundFinalizationEvent:
pingStop()
log.Info("a round finalization started")
@@ -1200,6 +1242,73 @@ func (a *covenantlessArkClient) handleRoundStream(
}
}
func (a *covenantlessArkClient) handleRoundSigningStarted(
ctx context.Context, ephemeralKey *secp256k1.PrivateKey, event client.RoundSigningStartedEvent,
) (signerSession bitcointree.SignerSession, err error) {
sweepClosure := bitcointree.CSVSigClosure{
Pubkey: a.AspPubkey,
Seconds: uint(a.RoundLifetime),
}
sweepTapLeaf, err := sweepClosure.Leaf()
if err != nil {
return
}
sweepTapTree := txscript.AssembleTaprootScriptTree(*sweepTapLeaf)
root := sweepTapTree.RootNode.TapHash()
signerSession = bitcointree.NewTreeSignerSession(
ephemeralKey, event.UnsignedTree, int64(a.MinRelayFee), root.CloneBytes(),
)
if err = signerSession.SetKeys(event.CosignersPublicKeys); err != nil {
return
}
nonces, err := signerSession.GetNonces()
if err != nil {
return
}
myPubKey := hex.EncodeToString(ephemeralKey.PubKey().SerializeCompressed())
err = a.arkClient.client.SendTreeNonces(ctx, event.ID, myPubKey, nonces)
return
}
func (a *covenantlessArkClient) handleRoundSigningNoncesGenerated(
ctx context.Context,
event client.RoundSigningNoncesGeneratedEvent,
ephemeralKey *secp256k1.PrivateKey,
signerSession bitcointree.SignerSession,
) error {
if signerSession == nil {
return fmt.Errorf("tree signer session not set")
}
if err := signerSession.SetAggregatedNonces(event.Nonces); err != nil {
return err
}
sigs, err := signerSession.Sign()
if err != nil {
return err
}
if err := a.arkClient.client.SendTreeSignatures(
ctx,
event.ID,
hex.EncodeToString(ephemeralKey.PubKey().SerializeCompressed()),
sigs,
); err != nil {
return err
}
return nil
}
func (a *covenantlessArkClient) handleRoundFinalization(
ctx context.Context, event client.RoundFinalizationEvent,
vtxos []client.Vtxo, receivers []client.Output,
@@ -1619,7 +1728,16 @@ func (a *covenantlessArkClient) selfTransferAllPendingPayments(
outputs := []client.Output{myself}
paymentID, err := a.client.RegisterPayment(ctx, inputs)
roundEphemeralKey, err := secp256k1.GeneratePrivateKey()
if err != nil {
return "", err
}
paymentID, err := a.client.RegisterPayment(
ctx,
inputs,
hex.EncodeToString(roundEphemeralKey.PubKey().SerializeCompressed()),
)
if err != nil {
return "", err
}
@@ -1629,7 +1747,7 @@ func (a *covenantlessArkClient) selfTransferAllPendingPayments(
}
roundTxid, err := a.handleRoundStream(
ctx, paymentID, pendingVtxos, outputs,
ctx, paymentID, pendingVtxos, outputs, roundEphemeralKey,
)
if err != nil {
return "", err

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,8 +41,10 @@ type covenantlessService struct {
eventsCh chan domain.RoundEvent
onboardingCh chan onboarding
// 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,10 +525,6 @@ 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))
@@ -449,21 +532,9 @@ func (s *covenantlessService) startFinalization() {
return
}
cosigners = append(cosigners, ephemeralKey)
cosignersPubKeys = append(cosignersPubKeys, ephemeralKey.PubKey())
}
cosigners = append(cosigners, ephemeralKey.PubKey())
aspSigningKey, 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")
return
}
cosigners = append(cosigners, aspSigningKey)
cosignersPubKeys = append(cosignersPubKeys, aspSigningKey.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,36 +566,49 @@ 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)
for _, seckey := range cosigners {
signer := bitcointree.NewTreeSignerSession(
seckey, tree, int64(s.minRelayFee), root.CloneBytes(),
aspSignerSession := bitcointree.NewTreeSignerSession(
ephemeralKey, tree, int64(s.minRelayFee), root.CloneBytes(),
)
// TODO nonces should be sent by the sender
nonces, err := signer.GetNonces()
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(seckey.PubKey(), nonces); err != nil {
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
}
signers = append(signers, signer)
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 {
@@ -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 {
log.Debugf("nonces aggregated for round %s", round.Id)
s.propagateRoundSigningNoncesGeneratedEvent(aggragatedNonces)
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
}
sig, err := signer.Sign()
if err != nil {
round.Fail(fmt.Errorf("failed to sign: %s", err))
log.WithError(err).Warn("failed to sign")
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
}
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
}
}
signedTree, err := coordinator.SignTree()
// 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"
)
@@ -22,6 +23,7 @@ type timedPayment struct {
type paymentsMap struct {
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 {

View File

@@ -3,14 +3,14 @@ package domain
import "github.com/ark-network/ark/common/tree"
type RoundEvent interface {
isEvent()
IsEvent()
}
func (r RoundStarted) isEvent() {}
func (r RoundFinalizationStarted) isEvent() {}
func (r RoundFinalized) isEvent() {}
func (r RoundFailed) isEvent() {}
func (r PaymentsRegistered) isEvent() {}
func (r RoundStarted) IsEvent() {}
func (r RoundFinalizationStarted) IsEvent() {}
func (r RoundFinalized) IsEvent() {}
func (r RoundFailed) IsEvent() {}
func (r PaymentsRegistered) IsEvent() {}
type RoundStarted struct {
Id string

View File

@@ -12,6 +12,7 @@ import (
"github.com/ark-network/ark/server/internal/core/domain"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
@@ -118,25 +119,77 @@ func (h *handler) Ping(ctx context.Context, req *arkv1.PingRequest) (*arkv1.Ping
return nil, status.Error(codes.InvalidArgument, "missing payment id")
}
forfeits, round, err := h.svc.UpdatePaymentStatus(ctx, req.GetPaymentId())
lastEvent, err := h.svc.UpdatePaymentStatus(ctx, req.GetPaymentId())
if err != nil {
return nil, err
}
var event *arkv1.RoundFinalizationEvent
if round != nil {
event = &arkv1.RoundFinalizationEvent{
Id: round.Id,
PoolTx: round.UnsignedTx,
ForfeitTxs: forfeits,
CongestionTree: castCongestionTree(round.CongestionTree),
Connectors: round.Connectors,
var resp *arkv1.PingResponse
switch e := lastEvent.(type) {
case domain.RoundFinalizationStarted:
resp = &arkv1.PingResponse{
Event: &arkv1.PingResponse_RoundFinalization{
RoundFinalization: &arkv1.RoundFinalizationEvent{
Id: e.Id,
PoolTx: e.PoolTx,
CongestionTree: castCongestionTree(e.CongestionTree),
ForfeitTxs: e.UnsignedForfeitTxs,
Connectors: e.Connectors,
},
},
}
case domain.RoundFinalized:
resp = &arkv1.PingResponse{
Event: &arkv1.PingResponse_RoundFinalized{
RoundFinalized: &arkv1.RoundFinalizedEvent{
Id: e.Id,
PoolTxid: e.Txid,
},
},
}
case domain.RoundFailed:
resp = &arkv1.PingResponse{
Event: &arkv1.PingResponse_RoundFailed{
RoundFailed: &arkv1.RoundFailed{
Id: e.Id,
Reason: e.Err,
},
},
}
case application.RoundSigningStarted:
cosignersKeys := make([]string, 0, len(e.Cosigners))
for _, key := range e.Cosigners {
cosignersKeys = append(cosignersKeys, hex.EncodeToString(key.SerializeCompressed()))
}
resp = &arkv1.PingResponse{
Event: &arkv1.PingResponse_RoundSigning{
RoundSigning: &arkv1.RoundSigningEvent{
Id: e.Id,
CosignersPubkeys: cosignersKeys,
UnsignedTree: castCongestionTree(e.UnsignedVtxoTree),
},
},
}
case application.RoundSigningNoncesGenerated:
serialized, err := e.SerializeNonces()
if err != nil {
logrus.WithError(err).Error("failed to serialize nonces")
return nil, status.Error(codes.Internal, "failed to serialize nonces")
}
resp = &arkv1.PingResponse{
Event: &arkv1.PingResponse_RoundSigningNoncesGenerated{
RoundSigningNoncesGenerated: &arkv1.RoundSigningNoncesGeneratedEvent{
Id: e.Id,
TreeNonces: serialized,
},
},
}
}
return &arkv1.PingResponse{
ForfeitTxs: forfeits,
Event: event,
}, nil
return resp, nil
}
func (h *handler) RegisterPayment(ctx context.Context, req *arkv1.RegisterPaymentRequest) (*arkv1.RegisterPaymentResponse, error) {
@@ -150,6 +203,13 @@ func (h *handler) RegisterPayment(ctx context.Context, req *arkv1.RegisterPaymen
return nil, err
}
pubkey := req.GetEphemeralPubkey()
if len(pubkey) > 0 {
if err := h.svc.RegisterCosignerPubkey(ctx, id, pubkey); err != nil {
return nil, err
}
}
return &arkv1.RegisterPaymentResponse{
Id: id,
}, nil
@@ -267,14 +327,6 @@ func (h *handler) GetEventStream(_ *arkv1.GetEventStreamRequest, stream arkv1.Ar
if err := stream.Send(ev); err != nil {
return err
}
switch ev.Event.(type) {
case *arkv1.GetEventStreamResponse_RoundFinalized, *arkv1.GetEventStreamResponse_RoundFailed:
if err := stream.Send(ev); err != nil {
return err
}
return nil
}
}
}
}
@@ -312,6 +364,74 @@ func (h *handler) GetInfo(ctx context.Context, req *arkv1.GetInfoRequest) (*arkv
}, nil
}
func (h *handler) SendTreeNonces(ctx context.Context, req *arkv1.SendTreeNoncesRequest) (*arkv1.SendTreeNoncesResponse, error) {
pubkey := req.GetPublicKey()
encodedNonces := req.GetTreeNonces()
roundID := req.GetRoundId()
if len(pubkey) <= 0 {
return nil, status.Error(codes.InvalidArgument, "missing cosigner public key")
}
if len(encodedNonces) <= 0 {
return nil, status.Error(codes.InvalidArgument, "missing tree nonces")
}
if len(roundID) <= 0 {
return nil, status.Error(codes.InvalidArgument, "missing round id")
}
pubkeyBytes, err := hex.DecodeString(pubkey)
if err != nil {
return nil, status.Error(codes.InvalidArgument, "invalid cosigner public key")
}
cosignerPublicKey, err := secp256k1.ParsePubKey(pubkeyBytes)
if err != nil {
return nil, status.Error(codes.InvalidArgument, "invalid cosigner public key")
}
if err := h.svc.RegisterCosignerNonces(ctx, roundID, cosignerPublicKey, encodedNonces); err != nil {
return nil, err
}
return &arkv1.SendTreeNoncesResponse{}, nil
}
func (h *handler) SendTreeSignatures(ctx context.Context, req *arkv1.SendTreeSignaturesRequest) (*arkv1.SendTreeSignaturesResponse, error) {
roundID := req.GetRoundId()
pubkey := req.GetPublicKey()
encodedSignatures := req.GetTreeSignatures()
if len(pubkey) <= 0 {
return nil, status.Error(codes.InvalidArgument, "missing cosigner public key")
}
if len(encodedSignatures) <= 0 {
return nil, status.Error(codes.InvalidArgument, "missing tree signatures")
}
if len(roundID) <= 0 {
return nil, status.Error(codes.InvalidArgument, "missing round id")
}
pubkeyBytes, err := hex.DecodeString(pubkey)
if err != nil {
return nil, status.Error(codes.InvalidArgument, "invalid cosigner public key")
}
cosignerPublicKey, err := secp256k1.ParsePubKey(pubkeyBytes)
if err != nil {
return nil, status.Error(codes.InvalidArgument, "invalid cosigner public key")
}
if err := h.svc.RegisterCosignerSignatures(ctx, roundID, cosignerPublicKey, encodedSignatures); err != nil {
return nil, err
}
return &arkv1.SendTreeSignaturesResponse{}, nil
}
func (h *handler) pushListener(l *listener) {
h.listenersLock.Lock()
defer h.listenersLock.Unlock()
@@ -368,6 +488,36 @@ func (h *handler) listenToEvents() {
},
},
}
case application.RoundSigningStarted:
cosignersKeys := make([]string, 0, len(e.Cosigners))
for _, key := range e.Cosigners {
cosignersKeys = append(cosignersKeys, hex.EncodeToString(key.SerializeCompressed()))
}
ev = &arkv1.GetEventStreamResponse{
Event: &arkv1.GetEventStreamResponse_RoundSigning{
RoundSigning: &arkv1.RoundSigningEvent{
Id: e.Id,
CosignersPubkeys: cosignersKeys,
UnsignedTree: castCongestionTree(e.UnsignedVtxoTree),
},
},
}
case application.RoundSigningNoncesGenerated:
serialized, err := e.SerializeNonces()
if err != nil {
logrus.WithError(err).Error("failed to serialize nonces")
continue
}
ev = &arkv1.GetEventStreamResponse{
Event: &arkv1.GetEventStreamResponse_RoundSigningNoncesGenerated{
RoundSigningNoncesGenerated: &arkv1.RoundSigningNoncesGeneratedEvent{
Id: e.Id,
TreeNonces: serialized,
},
},
}
}
if ev != nil {

View File

@@ -161,6 +161,14 @@ func Whitelist() map[string][]bakery.Op {
Entity: EntityHealth,
Action: "read",
}},
fmt.Sprintf("/%s/SendTreeNonces", arkv1.ArkService_ServiceDesc.ServiceName): {{
Entity: EntityArk,
Action: "write",
}},
fmt.Sprintf("/%s/SendTreeSignatures", arkv1.ArkService_ServiceDesc.ServiceName): {{
Entity: EntityArk,
Action: "write",
}},
}
}