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}": { "/v1/ping/{paymentId}": {
"get": { "get": {
"operationId": "ArkService_Ping", "operationId": "ArkService_Ping",
@@ -323,7 +387,6 @@
}, },
"/v1/round/{txid}": { "/v1/round/{txid}": {
"get": { "get": {
"summary": "TODO BTC: signTree rpc",
"operationId": "ArkService_GetRound", "operationId": "ArkService_GetRound",
"responses": { "responses": {
"200": { "200": {
@@ -502,14 +565,19 @@
"type": "object", "type": "object",
"properties": { "properties": {
"roundFinalization": { "roundFinalization": {
"$ref": "#/definitions/v1RoundFinalizationEvent", "$ref": "#/definitions/v1RoundFinalizationEvent"
"title": "TODO: BTC add \"signTree\" event"
}, },
"roundFinalized": { "roundFinalized": {
"$ref": "#/definitions/v1RoundFinalizedEvent" "$ref": "#/definitions/v1RoundFinalizedEvent"
}, },
"roundFailed": { "roundFailed": {
"$ref": "#/definitions/v1RoundFailed" "$ref": "#/definitions/v1RoundFailed"
},
"roundSigning": {
"$ref": "#/definitions/v1RoundSigningEvent"
},
"roundSigningNoncesGenerated": {
"$ref": "#/definitions/v1RoundSigningNoncesGeneratedEvent"
} }
} }
}, },
@@ -649,14 +717,20 @@
"v1PingResponse": { "v1PingResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
"forfeitTxs": { "roundFinalization": {
"type": "array",
"items": {
"type": "string"
}
},
"event": {
"$ref": "#/definitions/v1RoundFinalizationEvent" "$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", "type": "object",
"$ref": "#/definitions/v1Input" "$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": { "v1RoundStage": {
"type": "string", "type": "string",
"enum": [ "enum": [
@@ -777,6 +882,40 @@
], ],
"default": "ROUND_STAGE_UNSPECIFIED" "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": { "v1Tree": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@@ -17,13 +17,24 @@ service ArkService {
body: "*" 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) { rpc FinalizePayment(FinalizePaymentRequest) returns (FinalizePaymentResponse) {
option (google.api.http) = { option (google.api.http) = {
post: "/v1/payment/finalize" post: "/v1/payment/finalize"
body: "*" body: "*"
}; };
}; };
// TODO BTC: signTree rpc
rpc GetRound(GetRoundRequest) returns (GetRoundResponse) { rpc GetRound(GetRoundRequest) returns (GetRoundResponse) {
option (google.api.http) = { option (google.api.http) = {
get: "/v1/round/{txid}" get: "/v1/round/{txid}"
@@ -91,6 +102,7 @@ message CompletePaymentResponse {}
message RegisterPaymentRequest { message RegisterPaymentRequest {
repeated Input inputs = 1; repeated Input inputs = 1;
optional string ephemeral_pubkey = 2;
} }
message RegisterPaymentResponse { message RegisterPaymentResponse {
// Mocks wabisabi's credentials. // Mocks wabisabi's credentials.
@@ -128,10 +140,11 @@ message GetRoundByIdResponse {
message GetEventStreamRequest {} message GetEventStreamRequest {}
message GetEventStreamResponse { message GetEventStreamResponse {
oneof event { oneof event {
// TODO: BTC add "signTree" event
RoundFinalizationEvent round_finalization = 1; RoundFinalizationEvent round_finalization = 1;
RoundFinalizedEvent round_finalized = 2; RoundFinalizedEvent round_finalized = 2;
RoundFailed round_failed = 3; RoundFailed round_failed = 3;
RoundSigningEvent round_signing = 4;
RoundSigningNoncesGeneratedEvent round_signing_nonces_generated = 5;
} }
} }
@@ -139,8 +152,13 @@ message PingRequest {
string payment_id = 1; string payment_id = 1;
} }
message PingResponse { message PingResponse {
repeated string forfeit_txs = 1; oneof event {
RoundFinalizationEvent event = 2; RoundFinalizationEvent round_finalization = 1;
RoundFinalizedEvent round_finalized = 2;
RoundFailed round_failed = 3;
RoundSigningEvent round_signing = 4;
RoundSigningNoncesGeneratedEvent round_signing_nonces_generated = 5;
}
} }
message ListVtxosRequest { message ListVtxosRequest {
@@ -189,6 +207,17 @@ message RoundFailed {
string reason = 2; 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 // TYPES
enum RoundStage { enum RoundStage {
@@ -252,3 +281,19 @@ message PendingPayment {
string redeem_tx = 1; string redeem_tx = 1;
repeated string unconditional_forfeit_txs =2; 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) { 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 protoReq FinalizePaymentRequest
var metadata runtime.ServerMetadata 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) { mux.Handle("POST", pattern_ArkService_FinalizePayment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context()) ctx, cancel := context.WithCancel(req.Context())
defer cancel() 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) { mux.Handle("POST", pattern_ArkService_FinalizePayment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context()) ctx, cancel := context.WithCancel(req.Context())
defer cancel() 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_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_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"}, "")) 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_ClaimPayment_0 = runtime.ForwardResponseMessage
forward_ArkService_SendTreeNonces_0 = runtime.ForwardResponseMessage
forward_ArkService_SendTreeSignatures_0 = runtime.ForwardResponseMessage
forward_ArkService_FinalizePayment_0 = runtime.ForwardResponseMessage forward_ArkService_FinalizePayment_0 = runtime.ForwardResponseMessage
forward_ArkService_GetRound_0 = runtime.ForwardResponseMessage forward_ArkService_GetRound_0 = runtime.ForwardResponseMessage

View File

@@ -20,8 +20,9 @@ const _ = grpc.SupportPackageIsVersion7
type ArkServiceClient interface { type ArkServiceClient interface {
RegisterPayment(ctx context.Context, in *RegisterPaymentRequest, opts ...grpc.CallOption) (*RegisterPaymentResponse, error) RegisterPayment(ctx context.Context, in *RegisterPaymentRequest, opts ...grpc.CallOption) (*RegisterPaymentResponse, error)
ClaimPayment(ctx context.Context, in *ClaimPaymentRequest, opts ...grpc.CallOption) (*ClaimPaymentResponse, 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) 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) GetRound(ctx context.Context, in *GetRoundRequest, opts ...grpc.CallOption) (*GetRoundResponse, error)
GetRoundById(ctx context.Context, in *GetRoundByIdRequest, opts ...grpc.CallOption) (*GetRoundByIdResponse, error) GetRoundById(ctx context.Context, in *GetRoundByIdRequest, opts ...grpc.CallOption) (*GetRoundByIdResponse, error)
GetEventStream(ctx context.Context, in *GetEventStreamRequest, opts ...grpc.CallOption) (ArkService_GetEventStreamClient, 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 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) { func (c *arkServiceClient) FinalizePayment(ctx context.Context, in *FinalizePaymentRequest, opts ...grpc.CallOption) (*FinalizePaymentResponse, error) {
out := new(FinalizePaymentResponse) out := new(FinalizePaymentResponse)
err := c.cc.Invoke(ctx, "/ark.v1.ArkService/FinalizePayment", in, out, opts...) 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 { type ArkServiceServer interface {
RegisterPayment(context.Context, *RegisterPaymentRequest) (*RegisterPaymentResponse, error) RegisterPayment(context.Context, *RegisterPaymentRequest) (*RegisterPaymentResponse, error)
ClaimPayment(context.Context, *ClaimPaymentRequest) (*ClaimPaymentResponse, 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) FinalizePayment(context.Context, *FinalizePaymentRequest) (*FinalizePaymentResponse, error)
// TODO BTC: signTree rpc
GetRound(context.Context, *GetRoundRequest) (*GetRoundResponse, error) GetRound(context.Context, *GetRoundRequest) (*GetRoundResponse, error)
GetRoundById(context.Context, *GetRoundByIdRequest) (*GetRoundByIdResponse, error) GetRoundById(context.Context, *GetRoundByIdRequest) (*GetRoundByIdResponse, error)
GetEventStream(*GetEventStreamRequest, ArkService_GetEventStreamServer) error GetEventStream(*GetEventStreamRequest, ArkService_GetEventStreamServer) error
@@ -201,6 +221,12 @@ func (UnimplementedArkServiceServer) RegisterPayment(context.Context, *RegisterP
func (UnimplementedArkServiceServer) ClaimPayment(context.Context, *ClaimPaymentRequest) (*ClaimPaymentResponse, error) { func (UnimplementedArkServiceServer) ClaimPayment(context.Context, *ClaimPaymentRequest) (*ClaimPaymentResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ClaimPayment not implemented") 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) { func (UnimplementedArkServiceServer) FinalizePayment(context.Context, *FinalizePaymentRequest) (*FinalizePaymentResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method FinalizePayment not implemented") 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) 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) { func _ArkService_FinalizePayment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(FinalizePaymentRequest) in := new(FinalizePaymentRequest)
if err := dec(in); err != nil { if err := dec(in); err != nil {
@@ -477,6 +539,14 @@ var ArkService_ServiceDesc = grpc.ServiceDesc{
MethodName: "ClaimPayment", MethodName: "ClaimPayment",
Handler: _ArkService_ClaimPayment_Handler, Handler: _ArkService_ClaimPayment_Handler,
}, },
{
MethodName: "SendTreeNonces",
Handler: _ArkService_SendTreeNonces_Handler,
},
{
MethodName: "SendTreeSignatures",
Handler: _ArkService_SendTreeSignatures_Handler,
},
{ {
MethodName: "FinalizePayment", MethodName: "FinalizePayment",
Handler: _ArkService_FinalizePayment_Handler, Handler: _ArkService_FinalizePayment_Handler,

View File

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

View File

@@ -3,6 +3,7 @@ package covenantless
import ( import (
"bytes" "bytes"
"context" "context"
"encoding/hex"
"fmt" "fmt"
"io" "io"
"strings" "strings"
@@ -14,6 +15,7 @@ import (
"github.com/ark-network/ark/common/tree" "github.com/ark-network/ark/common/tree"
"github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/btcutil/psbt"
"github.com/btcsuite/btcd/txscript"
"github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"google.golang.org/grpc" "google.golang.org/grpc"
@@ -220,12 +222,15 @@ func castCongestionTree(congestionTree tree.CongestionTree) *arkv1.Tree {
func handleRoundStream( func handleRoundStream(
ctx *cli.Context, client arkv1.ArkServiceClient, paymentID string, ctx *cli.Context, client arkv1.ArkServiceClient, paymentID string,
vtxosToSign []vtxo, secKey *secp256k1.PrivateKey, receivers []*arkv1.Output, vtxosToSign []vtxo, secKey *secp256k1.PrivateKey, receivers []*arkv1.Output,
ephemeralKey *secp256k1.PrivateKey,
) (poolTxID string, err error) { ) (poolTxID string, err error) {
stream, err := client.GetEventStream(ctx.Context, &arkv1.GetEventStreamRequest{}) stream, err := client.GetEventStream(ctx.Context, &arkv1.GetEventStreamRequest{})
if err != nil { if err != nil {
return "", err return "", err
} }
myEphemeralPublicKey := hex.EncodeToString(ephemeralKey.PubKey().SerializeCompressed())
var pingStop func() var pingStop func()
pingReq := &arkv1.PingRequest{ pingReq := &arkv1.PingRequest{
PaymentId: paymentID, PaymentId: paymentID,
@@ -236,6 +241,8 @@ func handleRoundStream(
defer pingStop() defer pingStop()
var treeSignerSession bitcointree.SignerSession
for { for {
event, err := stream.Recv() event, err := stream.Recv()
if err == io.EOF { if err == io.EOF {
@@ -250,6 +257,143 @@ func handleRoundStream(
return "", fmt.Errorf("round failed: %s", e.GetReason()) 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 { if e := event.GetRoundFinalization(); e != nil {
// stop pinging as soon as we receive some forfeit txs // stop pinging as soon as we receive some forfeit txs
pingStop() pingStop()

View File

@@ -168,7 +168,11 @@ func (c *clArkBitcoinCLI) Onboard(ctx *cli.Context) error {
return err 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 return err
} }

View File

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

View File

@@ -1,7 +1,9 @@
package bitcointree package bitcointree
import ( import (
"bytes"
"errors" "errors"
"fmt"
"io" "io"
"strings" "strings"
@@ -12,20 +14,45 @@ import (
"github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/btcutil/psbt"
"github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
) )
var ( var (
ErrCongestionTreeNotSet = errors.New("congestion tree not set") ErrCongestionTreeNotSet = errors.New("congestion tree not set")
ErrAggregateKeyNotSet = errors.New("aggregate key 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 TreePartialSigs [][]*musig2.PartialSignature
type SignerSession interface { type SignerSession interface {
GetNonces() (TreeNonces, error) // generate of return cached nonce for this session GetNonces() (TreeNonces, error) // generate tree nonces for this session
SetKeys([]*btcec.PublicKey, TreeNonces) error // set the keys for this session (with the combined nonces) SetKeys([]*btcec.PublicKey) error // set the cosigner public keys for this session
Sign() (TreePartialSigs, error) // sign the tree SetAggregatedNonces(TreeNonces) error // set the aggregated nonces
Sign() (TreePartialSigs, error) // sign the tree
} }
type CoordinatorSession interface { type CoordinatorSession interface {
@@ -36,6 +63,34 @@ type CoordinatorSession interface {
SignTree() (tree.CongestionTree, error) 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( func AggregateKeys(
pubkeys []*btcec.PublicKey, pubkeys []*btcec.PublicKey,
scriptRoot []byte, scriptRoot []byte,
@@ -100,62 +155,6 @@ func ValidateTreeSigs(
return nil 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( func NewTreeSignerSession(
signer *btcec.PrivateKey, signer *btcec.PrivateKey,
congestionTree tree.CongestionTree, congestionTree tree.CongestionTree,
@@ -221,9 +220,9 @@ func (t *treeSignerSession) GetNonces() (TreeNonces, error) {
nonces := make(TreeNonces, 0) nonces := make(TreeNonces, 0)
for _, level := range t.myNonces { for _, level := range t.myNonces {
levelNonces := make([][66]byte, 0) levelNonces := make([]*Musig2Nonce, 0)
for _, nonce := range level { for _, nonce := range level {
levelNonces = append(levelNonces, nonce.PubNonce) levelNonces = append(levelNonces, &Musig2Nonce{nonce.PubNonce})
} }
nonces = append(nonces, levelNonces) nonces = append(nonces, levelNonces)
} }
@@ -231,15 +230,11 @@ func (t *treeSignerSession) GetNonces() (TreeNonces, error) {
return nonces, nil 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 { if t.keys != nil {
return errors.New("keys already set") return errors.New("keys already set")
} }
if t.aggregateNonces != nil {
return errors.New("nonces already set")
}
aggregateKey, err := AggregateKeys(keys, t.scriptRoot) aggregateKey, err := AggregateKeys(keys, t.scriptRoot)
if err != nil { if err != nil {
return err return err
@@ -251,12 +246,20 @@ func (t *treeSignerSession) SetKeys(keys []*btcec.PublicKey, nonces TreeNonces)
} }
t.prevoutFetcher = prevoutFetcher t.prevoutFetcher = prevoutFetcher
t.aggregateNonces = nonces
t.keys = keys t.keys = keys
return nil 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) { func (t *treeSignerSession) Sign() (TreePartialSigs, error) {
if t.tree == nil { if t.tree == nil {
return nil, ErrCongestionTreeNotSet return nil, ErrCongestionTreeNotSet
@@ -313,7 +316,7 @@ func (t *treeSignerSession) signPartial(partialTx *psbt.Packet, posx int, posy i
} }
return musig2.Sign( 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), musig2.WithSortedKeys(), musig2.WithTaprootSignTweak(t.scriptRoot),
) )
} }
@@ -390,12 +393,11 @@ func (t *treeCoordinatorSession) AggregateNonces() (TreeNonces, error) {
aggregatedNonces := make(TreeNonces, 0) aggregatedNonces := make(TreeNonces, 0)
for i, level := range t.tree { for i, level := range t.tree {
levelNonces := make([][66]byte, 0) levelNonces := make([]*Musig2Nonce, 0)
for j := range level { for j := range level {
nonces := make([][66]byte, 0) nonces := make([][66]byte, 0)
for _, n := range t.nonces { for _, n := range t.nonces {
nonces = append(nonces, n[i][j]) nonces = append(nonces, n[i][j].PubNonce)
} }
aggregatedNonce, err := musig2.AggregateNonces(nonces) aggregatedNonce, err := musig2.AggregateNonces(nonces)
@@ -403,7 +405,7 @@ func (t *treeCoordinatorSession) AggregateNonces() (TreeNonces, error) {
return nil, err return nil, err
} }
levelNonces = append(levelNonces, aggregatedNonce) levelNonces = append(levelNonces, &Musig2Nonce{aggregatedNonce})
} }
aggregatedNonces = append(aggregatedNonces, levelNonces) aggregatedNonces = append(aggregatedNonces, levelNonces)
@@ -414,12 +416,17 @@ func (t *treeCoordinatorSession) AggregateNonces() (TreeNonces, error) {
// SignTree implements CoordinatorSession. // SignTree implements CoordinatorSession.
func (t *treeCoordinatorSession) SignTree() (tree.CongestionTree, error) { func (t *treeCoordinatorSession) SignTree() (tree.CongestionTree, error) {
var missingSigs int
for _, sig := range t.sigs { for _, sig := range t.sigs {
if sig == nil { 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) aggregatedKey, err := AggregateKeys(t.keys, t.scriptRoot)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -432,9 +439,18 @@ func (t *treeCoordinatorSession) SignTree() (tree.CongestionTree, error) {
return nil, err return nil, err
} }
var combinedNonce *secp256k1.PublicKey
sigs := make([]*musig2.PartialSignature, 0) sigs := make([]*musig2.PartialSignature, 0)
for _, sig := range t.sigs { 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) inputFetcher := t.prevoutFetcher(partialTx)
@@ -448,7 +464,7 @@ func (t *treeCoordinatorSession) SignTree() (tree.CongestionTree, error) {
) )
combinedSig := musig2.CombineSigs( combinedSig := musig2.CombineSigs(
sigs[0].R, sigs, combinedNonce, sigs,
musig2.WithTaprootTweakedCombine([32]byte(message), t.keys, t.scriptRoot, true), musig2.WithTaprootTweakedCombine([32]byte(message), t.keys, t.scriptRoot, true),
) )
if err != nil { if err != nil {
@@ -507,3 +523,71 @@ type treePrevOutFetcher struct {
func (f *treePrevOutFetcher) FetchPrevOutput(wire.OutPoint) *wire.TxOut { func (f *treePrevOutFetcher) FetchPrevOutput(wire.OutPoint) *wire.TxOut {
return f.prevout 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 package bitcointree_test
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt"
"os" "os"
"testing" "testing"
@@ -85,6 +87,31 @@ func TestRoundTripSignTree(t *testing.T) {
aspNonces, err := aspSession.GetNonces() aspNonces, err := aspSession.GetNonces()
require.NoError(t, err) 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) err = aspCoordinator.AddNonce(alice.PubKey(), aliceNonces)
require.NoError(t, err) require.NoError(t, err)
@@ -101,18 +128,30 @@ func TestRoundTripSignTree(t *testing.T) {
err = aliceSession.SetKeys( err = aliceSession.SetKeys(
cosigners, cosigners,
)
require.NoError(t, err)
err = aliceSession.SetAggregatedNonces(
aggregatedNonce, aggregatedNonce,
) )
require.NoError(t, err) require.NoError(t, err)
err = bobSession.SetKeys( err = bobSession.SetKeys(
cosigners, cosigners,
)
require.NoError(t, err)
err = bobSession.SetAggregatedNonces(
aggregatedNonce, aggregatedNonce,
) )
require.NoError(t, err) require.NoError(t, err)
err = aspSession.SetKeys( err = aspSession.SetKeys(
cosigners, cosigners,
)
require.NoError(t, err)
err = aspSession.SetAggregatedNonces(
aggregatedNonce, aggregatedNonce,
) )
require.NoError(t, err) require.NoError(t, err)
@@ -126,6 +165,22 @@ func TestRoundTripSignTree(t *testing.T) {
aspSig, err := aspSession.Sign() aspSig, err := aspSession.Sign()
require.NoError(t, err) 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 // coordinator receives the signatures and combines them
err = aspCoordinator.AddSig(alice.PubKey(), aliceSig) err = aspCoordinator.AddSig(alice.PubKey(), aliceSig)
require.NoError(t, err) 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: compiles rest client from stub with https://github.com/go-swagger/go-swagger
genrest: genrest:
@echo "Generating rest client from stub..." @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: runs unit tests
test: test:

View File

@@ -4,7 +4,9 @@ import (
"context" "context"
"time" "time"
"github.com/ark-network/ark/common/bitcointree"
"github.com/ark-network/ark/common/tree" "github.com/ark-network/ark/common/tree"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
) )
const ( const (
@@ -25,7 +27,7 @@ type ASPClient interface {
ctx context.Context, tx, userPubkey string, congestionTree tree.CongestionTree, ctx context.Context, tx, userPubkey string, congestionTree tree.CongestionTree,
) error ) error
RegisterPayment( RegisterPayment(
ctx context.Context, inputs []VtxoKey, ctx context.Context, inputs []VtxoKey, ephemeralPublicKey string,
) (string, error) ) (string, error)
ClaimPayment( ClaimPayment(
ctx context.Context, paymentID string, outputs []Output, ctx context.Context, paymentID string, outputs []Output,
@@ -33,7 +35,7 @@ type ASPClient interface {
GetEventStream( GetEventStream(
ctx context.Context, paymentID string, ctx context.Context, paymentID string,
) (<-chan RoundEventChannel, error) ) (<-chan RoundEventChannel, error)
Ping(ctx context.Context, paymentID string) (*RoundFinalizationEvent, error) Ping(ctx context.Context, paymentID string) (RoundEvent, error)
FinalizePayment( FinalizePayment(
ctx context.Context, signedForfeitTxs []string, ctx context.Context, signedForfeitTxs []string,
) error ) error
@@ -43,6 +45,12 @@ type ASPClient interface {
CompletePayment( CompletePayment(
ctx context.Context, signedRedeemTx string, signedUnconditionalForfeitTxs []string, ctx context.Context, signedRedeemTx string, signedUnconditionalForfeitTxs []string,
) error ) error
SendTreeNonces(
ctx context.Context, roundID, cosignerPubkey string, nonces bitcointree.TreeNonces,
) error
SendTreeSignatures(
ctx context.Context, roundID, cosignerPubkey string, signatures bitcointree.TreePartialSigs,
) error
Close() Close()
} }
@@ -139,3 +147,18 @@ type RoundFailedEvent struct {
} }
func (e RoundFailedEvent) isRoundEvent() {} 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 package grpcclient
import ( import (
"bytes"
"context" "context"
"encoding/hex"
"fmt" "fmt"
"strings" "strings"
"time" "time"
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1" 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/common/tree"
"github.com/ark-network/ark/pkg/client-sdk/client" "github.com/ark-network/ark/pkg/client-sdk/client"
"github.com/ark-network/ark/pkg/client-sdk/internal/utils" "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"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
@@ -73,7 +77,13 @@ func (a *grpcClient) GetEventStream(
return 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( func (a *grpcClient) RegisterPayment(
ctx context.Context, inputs []client.VtxoKey, ctx context.Context, inputs []client.VtxoKey, ephemeralPublicKey string,
) (string, error) { ) (string, error) {
req := &arkv1.RegisterPaymentRequest{ req := &arkv1.RegisterPaymentRequest{
Inputs: ins(inputs).toProto(), Inputs: ins(inputs).toProto(),
} }
if len(ephemeralPublicKey) > 0 {
req.EphemeralPubkey = &ephemeralPublicKey
}
resp, err := a.svc.RegisterPayment(ctx, req) resp, err := a.svc.RegisterPayment(ctx, req)
if err != nil { if err != nil {
return "", err return "", err
@@ -171,7 +185,7 @@ func (a *grpcClient) ClaimPayment(
func (a *grpcClient) Ping( func (a *grpcClient) Ping(
ctx context.Context, paymentID string, ctx context.Context, paymentID string,
) (*client.RoundFinalizationEvent, error) { ) (client.RoundEvent, error) {
req := &arkv1.PingRequest{ req := &arkv1.PingRequest{
PaymentId: paymentID, PaymentId: paymentID,
} }
@@ -179,14 +193,8 @@ func (a *grpcClient) Ping(
if err != nil { if err != nil {
return nil, err return nil, err
} }
event := resp.GetEvent()
return &client.RoundFinalizationEvent{ return event{resp}.toRoundEvent()
ID: event.GetId(),
Tx: event.GetPoolTx(),
ForfeitTxs: event.GetForfeitTxs(),
Tree: treeFromProto{event.GetCongestionTree()}.parse(),
Connectors: event.GetConnectors(),
}, nil
} }
func (a *grpcClient) FinalizePayment( func (a *grpcClient) FinalizePayment(
@@ -252,6 +260,54 @@ func (a *grpcClient) GetRoundByID(
}, nil }, 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 type out client.Output
func (o out) toProto() *arkv1.Output { func (o out) toProto() *arkv1.Output {
@@ -271,16 +327,25 @@ func (o outs) toProto() []*arkv1.Output {
return list return list
} }
type event struct { // wrapper for GetEventStreamResponse and PingResponse
*arkv1.GetEventStreamResponse 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 { if ee := e.GetRoundFailed(); ee != nil {
return client.RoundFailedEvent{ return client.RoundFailedEvent{
ID: ee.GetId(), ID: ee.GetId(),
Reason: ee.GetReason(), Reason: ee.GetReason(),
} }, nil
} }
if ee := e.GetRoundFinalization(); ee != nil { if ee := e.GetRoundFinalization(); ee != nil {
tree := treeFromProto{ee.GetCongestionTree()}.parse() tree := treeFromProto{ee.GetCongestionTree()}.parse()
@@ -290,13 +355,49 @@ func (e event) toRoundEvent() client.RoundEvent {
ForfeitTxs: ee.GetForfeitTxs(), ForfeitTxs: ee.GetForfeitTxs(),
Tree: tree, Tree: tree,
Connectors: ee.GetConnectors(), Connectors: ee.GetConnectors(),
}, nil
}
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
} }
ee := e.GetRoundFinalized()
return client.RoundFinalizedEvent{ if ee := e.GetRoundSigningNoncesGenerated(); ee != nil {
ID: ee.GetId(), nonces, err := bitcointree.DecodeNonces(hex.NewDecoder(strings.NewReader(ee.GetTreeNonces())))
Txid: ee.GetPoolTxid(), if err != nil {
return nil, err
}
return client.RoundSigningNoncesGeneratedEvent{
ID: ee.GetId(),
Nonces: nonces,
}, nil
} }
return nil, fmt.Errorf("unknown event")
} }
type vtxo struct { type vtxo struct {

View File

@@ -1,7 +1,9 @@
package restclient package restclient
import ( import (
"bytes"
"context" "context"
"encoding/hex"
"fmt" "fmt"
"net/url" "net/url"
"strconv" "strconv"
@@ -9,16 +11,16 @@ import (
"time" "time"
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1" 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/common/tree"
"github.com/ark-network/ark/pkg/client-sdk/client" "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"
"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/arkservice/ark_service"
"github.com/ark-network/ark/pkg/client-sdk/client/rest/service/models" "github.com/ark-network/ark/pkg/client-sdk/client/rest/service/models"
"github.com/ark-network/ark/pkg/client-sdk/internal/utils" "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" httptransport "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt" "github.com/go-openapi/strfmt"
"github.com/vulpemventures/go-elements/psetv2"
) )
type restClient struct { type restClient struct {
@@ -71,39 +73,7 @@ func (a *restClient) GetEventStream(
if event != nil { if event != nil {
a.eventsCh <- client.RoundEventChannel{ a.eventsCh <- client.RoundEventChannel{
Event: *event, 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)
} }
} }
@@ -286,7 +256,7 @@ func (a *restClient) Onboard(
} }
func (a *restClient) RegisterPayment( func (a *restClient) RegisterPayment(
ctx context.Context, inputs []client.VtxoKey, ctx context.Context, inputs []client.VtxoKey, ephemeralPublicKey string,
) (string, error) { ) (string, error) {
ins := make([]*models.V1Input, 0, len(inputs)) ins := make([]*models.V1Input, 0, len(inputs))
for _, i := range inputs { for _, i := range inputs {
@@ -295,11 +265,15 @@ func (a *restClient) RegisterPayment(
Vout: int64(i.VOut), Vout: int64(i.VOut),
}) })
} }
body := models.V1RegisterPaymentRequest{ body := &models.V1RegisterPaymentRequest{
Inputs: ins, Inputs: ins,
} }
if len(ephemeralPublicKey) > 0 {
body.EphemeralPubkey = ephemeralPublicKey
}
resp, err := a.svc.ArkServiceRegisterPayment( resp, err := a.svc.ArkServiceRegisterPayment(
ark_service.NewArkServiceRegisterPaymentParams().WithBody(&body), ark_service.NewArkServiceRegisterPaymentParams().WithBody(body),
) )
if err != nil { if err != nil {
return "", err return "", err
@@ -331,7 +305,7 @@ func (a *restClient) ClaimPayment(
func (a *restClient) Ping( func (a *restClient) Ping(
ctx context.Context, paymentID string, ctx context.Context, paymentID string,
) (*client.RoundFinalizationEvent, error) { ) (client.RoundEvent, error) {
r := ark_service.NewArkServicePingParams() r := ark_service.NewArkServicePingParams()
r.SetPaymentID(paymentID) r.SetPaymentID(paymentID)
resp, err := a.svc.ArkServicePing(r) resp, err := a.svc.ArkServicePing(r)
@@ -339,18 +313,65 @@ func (a *restClient) Ping(
return nil, err return nil, err
} }
var event *client.RoundFinalizationEvent payload := resp.Payload
if resp.Payload.Event != nil {
event = &client.RoundFinalizationEvent{ if e := payload.RoundFailed; e != nil {
ID: resp.Payload.Event.ID, return client.RoundFailedEvent{
Tx: resp.Payload.Event.PoolTx, ID: e.ID,
ForfeitTxs: resp.Payload.Event.ForfeitTxs, Reason: e.Reason,
Tree: treeFromProto{resp.Payload.Event.CongestionTree}.parse(), }, nil
Connectors: resp.Payload.Event.Connectors, }
} 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( func (a *restClient) FinalizePayment(
@@ -454,6 +475,58 @@ func (a *restClient) GetRoundByID(
}, nil }, 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( func newRestClient(
serviceURL string, serviceURL string,
) (ark_service.ClientService, error) { ) (ark_service.ClientService, error) {
@@ -551,13 +624,3 @@ func (t treeToProto) parse() *models.V1Tree {
Levels: levels, 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) 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) 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) { func (a *Client) ArkServiceGetRound(params *ArkServiceGetRoundParams, opts ...ClientOption) (*ArkServiceGetRoundOK, error) {
// TODO: Validate the params before sending // 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()) 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 // SetTransport changes the transport on the client
func (a *Client) SetTransport(transport runtime.ClientTransport) { func (a *Client) SetTransport(transport runtime.ClientTransport) {
a.transport = transport 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 // round failed
RoundFailed *V1RoundFailed `json:"roundFailed,omitempty"` RoundFailed *V1RoundFailed `json:"roundFailed,omitempty"`
// TODO: BTC add "signTree" event // round finalization
RoundFinalization *V1RoundFinalizationEvent `json:"roundFinalization,omitempty"` RoundFinalization *V1RoundFinalizationEvent `json:"roundFinalization,omitempty"`
// round finalized // round finalized
RoundFinalized *V1RoundFinalizedEvent `json:"roundFinalized,omitempty"` 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 // Validate validates this v1 get event stream response
@@ -44,6 +50,14 @@ func (m *V1GetEventStreamResponse) Validate(formats strfmt.Registry) error {
res = append(res, err) 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 { if len(res) > 0 {
return errors.CompositeValidationError(res...) return errors.CompositeValidationError(res...)
} }
@@ -107,6 +121,44 @@ func (m *V1GetEventStreamResponse) validateRoundFinalized(formats strfmt.Registr
return nil 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 // 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 { func (m *V1GetEventStreamResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error var res []error
@@ -123,6 +175,14 @@ func (m *V1GetEventStreamResponse) ContextValidate(ctx context.Context, formats
res = append(res, err) 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 { if len(res) > 0 {
return errors.CompositeValidationError(res...) return errors.CompositeValidationError(res...)
} }
@@ -192,6 +252,48 @@ func (m *V1GetEventStreamResponse) contextValidateRoundFinalized(ctx context.Con
return nil 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 // MarshalBinary interface implementation
func (m *V1GetEventStreamResponse) MarshalBinary() ([]byte, error) { func (m *V1GetEventStreamResponse) MarshalBinary() ([]byte, error) {
if m == nil { if m == nil {

View File

@@ -18,18 +18,43 @@ import (
// swagger:model v1PingResponse // swagger:model v1PingResponse
type V1PingResponse struct { type V1PingResponse struct {
// event // round failed
Event *V1RoundFinalizationEvent `json:"event,omitempty"` RoundFailed *V1RoundFailed `json:"roundFailed,omitempty"`
// forfeit txs // round finalization
ForfeitTxs []string `json:"forfeitTxs"` 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 // Validate validates this v1 ping response
func (m *V1PingResponse) Validate(formats strfmt.Registry) error { func (m *V1PingResponse) Validate(formats strfmt.Registry) error {
var res []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) res = append(res, err)
} }
@@ -39,17 +64,93 @@ func (m *V1PingResponse) Validate(formats strfmt.Registry) error {
return nil return nil
} }
func (m *V1PingResponse) validateEvent(formats strfmt.Registry) error { func (m *V1PingResponse) validateRoundFailed(formats strfmt.Registry) error {
if swag.IsZero(m.Event) { // not required if swag.IsZero(m.RoundFailed) { // not required
return nil return nil
} }
if m.Event != nil { if m.RoundFailed != nil {
if err := m.Event.Validate(formats); err != nil { if err := m.RoundFailed.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok { if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("event") return ve.ValidateName("roundFailed")
} else if ce, ok := err.(*errors.CompositeError); ok { } 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 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 { func (m *V1PingResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []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) res = append(res, err)
} }
@@ -72,19 +189,103 @@ func (m *V1PingResponse) ContextValidate(ctx context.Context, formats strfmt.Reg
return nil 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 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 { if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("event") return ve.ValidateName("roundFailed")
} else if ce, ok := err.(*errors.CompositeError); ok { } 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 return err
} }

View File

@@ -19,6 +19,9 @@ import (
// swagger:model v1RegisterPaymentRequest // swagger:model v1RegisterPaymentRequest
type V1RegisterPaymentRequest struct { type V1RegisterPaymentRequest struct {
// ephemeral pubkey
EphemeralPubkey string `json:"ephemeralPubkey,omitempty"`
// inputs // inputs
Inputs []*V1Input `json:"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 { if err != nil {
return "", err return "", err
} }
@@ -796,7 +796,7 @@ func (a *covenantArkClient) sendOffchain(
} }
paymentID, err := a.client.RegisterPayment( paymentID, err := a.client.RegisterPayment(
ctx, inputs, ctx, inputs, "", // ephemeralPublicKey is not required for covenant
) )
if err != nil { if err != nil {
return "", err return "", err

View File

@@ -262,7 +262,11 @@ func (a *covenantlessArkClient) Onboard(
return "", err 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 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 { if err != nil {
return "", err return "", err
} }
@@ -618,7 +631,7 @@ func (a *covenantlessArkClient) CollaborativeRedeem(
} }
poolTxID, err := a.handleRoundStream( poolTxID, err := a.handleRoundStream(
ctx, paymentID, selectedCoins, receivers, ctx, paymentID, selectedCoins, receivers, roundEphemeralKey,
) )
if err != nil { if err != nil {
return "", err return "", err
@@ -990,8 +1003,13 @@ func (a *covenantlessArkClient) sendOffchain(
}) })
} }
roundEphemeralKey, err := secp256k1.GeneratePrivateKey()
if err != nil {
return "", err
}
paymentID, err := a.client.RegisterPayment( paymentID, err := a.client.RegisterPayment(
ctx, inputs, ctx, inputs, hex.EncodeToString(roundEphemeralKey.PubKey().SerializeCompressed()),
) )
if err != nil { if err != nil {
return "", err return "", err
@@ -1006,7 +1024,7 @@ func (a *covenantlessArkClient) sendOffchain(
log.Infof("payment registered with id: %s", paymentID) log.Infof("payment registered with id: %s", paymentID)
poolTxID, err := a.handleRoundStream( poolTxID, err := a.handleRoundStream(
ctx, paymentID, selectedCoins, receiversOutput, ctx, paymentID, selectedCoins, receiversOutput, roundEphemeralKey,
) )
if err != nil { if err != nil {
return "", err return "", err
@@ -1144,7 +1162,10 @@ func (a *covenantlessArkClient) addVtxoInput(
func (a *covenantlessArkClient) handleRoundStream( func (a *covenantlessArkClient) handleRoundStream(
ctx context.Context, ctx context.Context,
paymentID string, vtxosToSign []client.Vtxo, receivers []client.Output, paymentID string,
vtxosToSign []client.Vtxo,
receivers []client.Output,
roundEphemeralKey *secp256k1.PrivateKey,
) (string, error) { ) (string, error) {
eventsCh, err := a.client.GetEventStream(ctx, paymentID) eventsCh, err := a.client.GetEventStream(ctx, paymentID)
if err != nil { if err != nil {
@@ -1158,6 +1179,8 @@ func (a *covenantlessArkClient) handleRoundStream(
defer pingStop() defer pingStop()
var signerSession bitcointree.SignerSession
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
@@ -1172,6 +1195,25 @@ func (a *covenantlessArkClient) handleRoundStream(
return event.(client.RoundFinalizedEvent).Txid, nil return event.(client.RoundFinalizedEvent).Txid, nil
case client.RoundFailedEvent: case client.RoundFailedEvent:
return "", fmt.Errorf("round failed: %s", event.(client.RoundFailedEvent).Reason) 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: case client.RoundFinalizationEvent:
pingStop() pingStop()
log.Info("a round finalization started") 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( func (a *covenantlessArkClient) handleRoundFinalization(
ctx context.Context, event client.RoundFinalizationEvent, ctx context.Context, event client.RoundFinalizationEvent,
vtxos []client.Vtxo, receivers []client.Output, vtxos []client.Vtxo, receivers []client.Output,
@@ -1619,7 +1728,16 @@ func (a *covenantlessArkClient) selfTransferAllPendingPayments(
outputs := []client.Output{myself} 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 { if err != nil {
return "", err return "", err
} }
@@ -1629,7 +1747,7 @@ func (a *covenantlessArkClient) selfTransferAllPendingPayments(
} }
roundTxid, err := a.handleRoundStream( roundTxid, err := a.handleRoundStream(
ctx, paymentID, pendingVtxos, outputs, ctx, paymentID, pendingVtxos, outputs, roundEphemeralKey,
) )
if err != nil { if err != nil {
return "", err return "", err

View File

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

View File

@@ -41,9 +41,11 @@ type covenantlessService struct {
eventsCh chan domain.RoundEvent eventsCh chan domain.RoundEvent
onboardingCh chan onboarding onboardingCh chan onboarding
currentRound *domain.Round // cached data for the current round
lastEvent domain.RoundEvent
asyncPaymentsCache map[domain.VtxoKey]struct { currentRound *domain.Round
treeSigningSessions map[string]*musigSigningSession
asyncPaymentsCache map[domain.VtxoKey]struct {
receivers []domain.Receiver receivers []domain.Receiver
expireAt int64 expireAt int64
} }
@@ -89,6 +91,7 @@ func NewCovenantlessService(
eventsCh: eventsCh, eventsCh: eventsCh,
onboardingCh: onboardingCh, onboardingCh: onboardingCh,
asyncPaymentsCache: asyncPaymentsCache, asyncPaymentsCache: asyncPaymentsCache,
treeSigningSessions: make(map[string]*musigSigningSession),
} }
repoManager.RegisterEventsHandler( repoManager.RegisterEventsHandler(
@@ -273,17 +276,17 @@ func (s *covenantlessService) ClaimVtxos(ctx context.Context, creds string, rece
return s.paymentRequests.update(*payment) 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) err := s.paymentRequests.updatePingTimestamp(id)
if err != nil { if err != nil {
if _, ok := err.(errPaymentNotFound); ok { 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 { func (s *covenantlessService) SignVtxos(ctx context.Context, forfeitTxs []string) error {
@@ -367,6 +370,78 @@ func (s *covenantlessService) Onboard(
return nil 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() { func (s *covenantlessService) start() {
s.startRound() s.startRound()
} }
@@ -375,6 +450,7 @@ func (s *covenantlessService) startRound() {
round := domain.NewRound(dustAmount) // TODO dynamic dust amount? round := domain.NewRound(dustAmount) // TODO dynamic dust amount?
//nolint:all //nolint:all
round.StartRegistration() round.StartRegistration()
s.lastEvent = nil
s.currentRound = round s.currentRound = round
defer func() { defer func() {
@@ -389,8 +465,12 @@ func (s *covenantlessService) startFinalization() {
ctx := context.Background() ctx := context.Background()
round := s.currentRound round := s.currentRound
roundRemainingDuration := time.Duration(s.roundInterval/2-1) * time.Second
thirdOfRemainingDuration := time.Duration(roundRemainingDuration / 3)
var roundAborted bool var roundAborted bool
defer func() { defer func() {
delete(s.treeSigningSessions, round.Id)
if roundAborted { if roundAborted {
s.startRound() s.startRound()
return return
@@ -404,7 +484,7 @@ func (s *covenantlessService) startFinalization() {
s.startRound() s.startRound()
return return
} }
time.Sleep(time.Duration((s.roundInterval/2)-1) * time.Second) time.Sleep(thirdOfRemainingDuration)
s.finalizeRound() s.finalizeRound()
}() }()
@@ -424,7 +504,14 @@ func (s *covenantlessService) startFinalization() {
if num > paymentsThreshold { if num > paymentsThreshold {
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 { if _, err := round.RegisterPayments(payments); err != nil {
round.Fail(fmt.Errorf("failed to register payments: %s", err)) round.Fail(fmt.Errorf("failed to register payments: %s", err))
log.WithError(err).Warn("failed to register payments") log.WithError(err).Warn("failed to register payments")
@@ -438,32 +525,16 @@ func (s *covenantlessService) startFinalization() {
return return
} }
cosigners := make([]*secp256k1.PrivateKey, 0) ephemeralKey, err := secp256k1.GeneratePrivateKey()
cosignersPubKeys := make([]*secp256k1.PublicKey, 0, len(cosigners))
for range payments {
// TODO sender should provide the ephemeral *public* key
ephemeralKey, err := secp256k1.GeneratePrivateKey()
if err != nil {
round.Fail(fmt.Errorf("failed to generate ephemeral key: %s", err))
log.WithError(err).Warn("failed to generate ephemeral key")
return
}
cosigners = append(cosigners, ephemeralKey)
cosignersPubKeys = append(cosignersPubKeys, ephemeralKey.PubKey())
}
aspSigningKey, err := secp256k1.GeneratePrivateKey()
if err != nil { if err != nil {
round.Fail(fmt.Errorf("failed to generate asp signing key: %s", err)) round.Fail(fmt.Errorf("failed to generate ephemeral key: %s", err))
log.WithError(err).Warn("failed to generate asp signing key") log.WithError(err).Warn("failed to generate ephemeral key")
return return
} }
cosigners = append(cosigners, aspSigningKey) cosigners = append(cosigners, ephemeralKey.PubKey())
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 { if err != nil {
round.Fail(fmt.Errorf("failed to create pool tx: %s", err)) round.Fail(fmt.Errorf("failed to create pool tx: %s", err))
log.WithError(err).Warn("failed to create pool tx") 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) log.Debugf("pool tx created for round %s", round.Id)
if len(tree) > 0 { 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{ sweepClosure := bitcointree.CSVSigClosure{
Pubkey: s.pubkey, Pubkey: s.pubkey,
Seconds: uint(s.roundLifetime), Seconds: uint(s.roundLifetime),
@@ -485,37 +566,50 @@ func (s *covenantlessService) startFinalization() {
sweepTapTree := txscript.AssembleTaprootScriptTree(*sweepTapLeaf) sweepTapTree := txscript.AssembleTaprootScriptTree(*sweepTapLeaf)
root := sweepTapTree.RootNode.TapHash() root := sweepTapTree.RootNode.TapHash()
coordinator, err := s.createTreeCoordinatorSession(tree, cosignersPubKeys, root) coordinator, err := s.createTreeCoordinatorSession(tree, cosigners, root)
if err != nil { if err != nil {
round.Fail(fmt.Errorf("failed to create tree coordinator: %s", err)) round.Fail(fmt.Errorf("failed to create tree coordinator: %s", err))
log.WithError(err).Warn("failed to create tree coordinator") log.WithError(err).Warn("failed to create tree coordinator")
return return
} }
signers := make([]bitcointree.SignerSession, 0) aspSignerSession := bitcointree.NewTreeSignerSession(
ephemeralKey, tree, int64(s.minRelayFee), root.CloneBytes(),
)
for _, seckey := range cosigners { nonces, err := aspSignerSession.GetNonces()
signer := bitcointree.NewTreeSignerSession( if err != nil {
seckey, tree, int64(s.minRelayFee), root.CloneBytes(), round.Fail(fmt.Errorf("failed to get nonces: %s", err))
) log.WithError(err).Warn("failed to get nonces")
return
// TODO nonces should be sent by the sender
nonces, err := signer.GetNonces()
if err != nil {
round.Fail(fmt.Errorf("failed to get nonces: %s", err))
log.WithError(err).Warn("failed to get nonces")
return
}
if err := coordinator.AddNonce(seckey.PubKey(), nonces); err != nil {
round.Fail(fmt.Errorf("failed to add nonce: %s", err))
log.WithError(err).Warn("failed to add nonce")
return
}
signers = append(signers, signer)
} }
if err := coordinator.AddNonce(ephemeralKey.PubKey(), nonces); err != nil {
round.Fail(fmt.Errorf("failed to add nonce: %s", err))
log.WithError(err).Warn("failed to add nonce")
return
}
noncesTimer := time.NewTimer(thirdOfRemainingDuration)
select {
case <-noncesTimer.C:
round.Fail(fmt.Errorf("musig2 signing session timed out (nonce collection)"))
log.Warn("musig2 signing session timed out (nonce collection)")
return
case <-signingSession.nonceDoneC:
noncesTimer.Stop()
for pubkey, nonce := range signingSession.nonces {
if err := coordinator.AddNonce(pubkey, nonce); err != nil {
round.Fail(fmt.Errorf("failed to add nonce: %s", err))
log.WithError(err).Warn("failed to add nonce")
return
}
}
}
log.Debugf("nonces collected for round %s", round.Id)
aggragatedNonces, err := coordinator.AggregateNonces() aggragatedNonces, err := coordinator.AggregateNonces()
if err != nil { if err != nil {
round.Fail(fmt.Errorf("failed to aggregate nonces: %s", err)) round.Fail(fmt.Errorf("failed to aggregate nonces: %s", err))
@@ -523,36 +617,69 @@ func (s *covenantlessService) startFinalization() {
return return
} }
// TODO aggragated nonces and public keys should be sent back to signer log.Debugf("nonces aggregated for round %s", round.Id)
// TODO signing should be done client-side (except for the ASP)
for i, signer := range signers {
if err := signer.SetKeys(cosignersPubKeys, aggragatedNonces); err != nil {
round.Fail(fmt.Errorf("failed to set keys: %s", err))
log.WithError(err).Warn("failed to set keys")
return
}
sig, err := signer.Sign() s.propagateRoundSigningNoncesGeneratedEvent(aggragatedNonces)
if err != nil {
round.Fail(fmt.Errorf("failed to sign: %s", err))
log.WithError(err).Warn("failed to sign")
return
}
if err := coordinator.AddSig(cosignersPubKeys[i], sig); err != nil { if err := aspSignerSession.SetKeys(cosigners); err != nil {
round.Fail(fmt.Errorf("failed to add sig: %s", err)) round.Fail(fmt.Errorf("failed to set keys: %s", err))
log.WithError(err).Warn("failed to add sig") log.WithError(err).Warn("failed to set keys")
return return
}
} }
signedTree, err := coordinator.SignTree() if err := aspSignerSession.SetAggregatedNonces(aggragatedNonces); err != nil {
round.Fail(fmt.Errorf("failed to set aggregated nonces: %s", err))
log.WithError(err).Warn("failed to set aggregated nonces")
return
}
// sign the tree as ASP
aspTreeSigs, err := aspSignerSession.Sign()
if err != nil { if err != nil {
round.Fail(fmt.Errorf("failed to sign tree: %s", err)) round.Fail(fmt.Errorf("failed to sign tree: %s", err))
log.WithError(err).Warn("failed to sign tree") log.WithError(err).Warn("failed to sign tree")
return 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 tree = signedTree
} }
@@ -578,6 +705,29 @@ func (s *covenantlessService) startFinalization() {
log.Debugf("started finalization stage for round: %s", round.Id) 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( func (s *covenantlessService) createTreeCoordinatorSession(
congestionTree tree.CongestionTree, cosigners []*secp256k1.PublicKey, root chainhash.Hash, congestionTree tree.CongestionTree, cosigners []*secp256k1.PublicKey, root chainhash.Hash,
) (bitcointree.CoordinatorSession, error) { ) (bitcointree.CoordinatorSession, error) {
@@ -900,14 +1050,17 @@ func (s *covenantlessService) propagateEvents(round *domain.Round) {
switch e := lastEvent.(type) { switch e := lastEvent.(type) {
case domain.RoundFinalizationStarted: case domain.RoundFinalizationStarted:
forfeitTxs := s.forfeitTxs.view() forfeitTxs := s.forfeitTxs.view()
s.eventsCh <- domain.RoundFinalizationStarted{ ev := domain.RoundFinalizationStarted{
Id: e.Id, Id: e.Id,
CongestionTree: e.CongestionTree, CongestionTree: e.CongestionTree,
Connectors: e.Connectors, Connectors: e.Connectors,
PoolTx: e.PoolTx, PoolTx: e.PoolTx,
UnsignedForfeitTxs: forfeitTxs, UnsignedForfeitTxs: forfeitTxs,
} }
s.lastEvent = ev
s.eventsCh <- ev
case domain.RoundFinalized, domain.RoundFailed: case domain.RoundFinalized, domain.RoundFailed:
s.lastEvent = e
s.eventsCh <- e s.eventsCh <- e
} }
} }
@@ -1118,3 +1271,26 @@ func findForfeitTxBitcoin(
return "", fmt.Errorf("forfeit tx not found") 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 { for _, input := range inputs {
// sweepableVtxos related to the sweep input // sweepableVtxos related to the sweep input
sweepableVtxos := make([]domain.VtxoKey, 0) sweepableVtxos := make([]domain.VtxoKey, 0)
fmt.Println("input", input.GetHash().String(), input.GetIndex())
// check if input is the vtxo itself // check if input is the vtxo itself
vtxos, _ := s.repoManager.Vtxos().GetVtxos( vtxos, _ := s.repoManager.Vtxos().GetVtxos(

View File

@@ -25,7 +25,7 @@ type Service interface {
GetEventsChannel(ctx context.Context) <-chan domain.RoundEvent GetEventsChannel(ctx context.Context) <-chan domain.RoundEvent
UpdatePaymentStatus( UpdatePaymentStatus(
ctx context.Context, paymentId string, ctx context.Context, paymentId string,
) (unsignedForfeitTxs []string, currentRound *domain.Round, err error) ) (lastEvent domain.RoundEvent, err error)
ListVtxos( ListVtxos(
ctx context.Context, pubkey *secp256k1.PublicKey, ctx context.Context, pubkey *secp256k1.PublicKey,
) (spendableVtxos, spentVtxos []domain.Vtxo, err error) ) (spendableVtxos, spentVtxos []domain.Vtxo, err error)
@@ -41,6 +41,16 @@ type Service interface {
CompleteAsyncPayment( CompleteAsyncPayment(
ctx context.Context, redeemTx string, unconditionalForfeitTxs []string, ctx context.Context, redeemTx string, unconditionalForfeitTxs []string,
) error ) 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 { type ServiceInfo struct {

View File

@@ -10,6 +10,7 @@ import (
"github.com/ark-network/ark/common/tree" "github.com/ark-network/ark/common/tree"
"github.com/ark-network/ark/server/internal/core/domain" "github.com/ark-network/ark/server/internal/core/domain"
"github.com/ark-network/ark/server/internal/core/ports" "github.com/ark-network/ark/server/internal/core/ports"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@@ -20,8 +21,9 @@ type timedPayment struct {
} }
type paymentsMap struct { type paymentsMap struct {
lock *sync.RWMutex lock *sync.RWMutex
payments map[string]*timedPayment payments map[string]*timedPayment
ephemeralKeys map[string]*secp256k1.PublicKey
} }
func newPaymentsMap(payments []domain.Payment) *paymentsMap { 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{}} paymentsById[p.Id] = &timedPayment{p, time.Now(), time.Time{}}
} }
lock := &sync.RWMutex{} lock := &sync.RWMutex{}
return &paymentsMap{lock, paymentsById} return &paymentsMap{lock, paymentsById, make(map[string]*secp256k1.PublicKey)}
} }
func (m *paymentsMap) len() int64 { func (m *paymentsMap) len() int64 {
@@ -46,6 +48,18 @@ func (m *paymentsMap) len() int64 {
return count 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 { func (m *paymentsMap) push(payment domain.Payment) error {
m.lock.Lock() m.lock.Lock()
defer m.lock.Unlock() defer m.lock.Unlock()
@@ -58,7 +72,19 @@ func (m *paymentsMap) push(payment domain.Payment) error {
return nil 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() m.lock.Lock()
defer m.lock.Unlock() defer m.lock.Unlock()
@@ -83,11 +109,16 @@ func (m *paymentsMap) pop(num int64) []domain.Payment {
} }
payments := make([]domain.Payment, 0, num) payments := make([]domain.Payment, 0, num)
cosigners := make([]*secp256k1.PublicKey, 0, num)
for _, p := range paymentsByTime[:num] { for _, p := range paymentsByTime[:num] {
payments = append(payments, p.Payment) 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) delete(m.payments, p.Id)
} }
return payments return payments, cosigners
} }
func (m *paymentsMap) update(payment domain.Payment) error { func (m *paymentsMap) update(payment domain.Payment) error {

View File

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

View File

@@ -12,6 +12,7 @@ import (
"github.com/ark-network/ark/server/internal/core/domain" "github.com/ark-network/ark/server/internal/core/domain"
"github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/sirupsen/logrus"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "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") 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 { if err != nil {
return nil, err return nil, err
} }
var event *arkv1.RoundFinalizationEvent var resp *arkv1.PingResponse
if round != nil {
event = &arkv1.RoundFinalizationEvent{ switch e := lastEvent.(type) {
Id: round.Id, case domain.RoundFinalizationStarted:
PoolTx: round.UnsignedTx, resp = &arkv1.PingResponse{
ForfeitTxs: forfeits, Event: &arkv1.PingResponse_RoundFinalization{
CongestionTree: castCongestionTree(round.CongestionTree), RoundFinalization: &arkv1.RoundFinalizationEvent{
Connectors: round.Connectors, 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, return resp, nil
Event: event,
}, nil
} }
func (h *handler) RegisterPayment(ctx context.Context, req *arkv1.RegisterPaymentRequest) (*arkv1.RegisterPaymentResponse, error) { 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 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{ return &arkv1.RegisterPaymentResponse{
Id: id, Id: id,
}, nil }, nil
@@ -267,14 +327,6 @@ func (h *handler) GetEventStream(_ *arkv1.GetEventStreamRequest, stream arkv1.Ar
if err := stream.Send(ev); err != nil { if err := stream.Send(ev); err != nil {
return err 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 }, 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) { func (h *handler) pushListener(l *listener) {
h.listenersLock.Lock() h.listenersLock.Lock()
defer h.listenersLock.Unlock() 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 { if ev != nil {

View File

@@ -161,6 +161,14 @@ func Whitelist() map[string][]bakery.Op {
Entity: EntityHealth, Entity: EntityHealth,
Action: "read", 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",
}},
} }
} }