diff --git a/api-spec/openapi/swagger/ark/v1/service.swagger.json b/api-spec/openapi/swagger/ark/v1/service.swagger.json index 3754e1c..3141115 100644 --- a/api-spec/openapi/swagger/ark/v1/service.swagger.json +++ b/api-spec/openapi/swagger/ark/v1/service.swagger.json @@ -651,6 +651,9 @@ }, "descriptor": { "type": "string" + }, + "tapscripts": { + "$ref": "#/definitions/v1Tapscripts" } } }, @@ -748,6 +751,9 @@ }, "descriptor": { "type": "string" + }, + "tapscripts": { + "$ref": "#/definitions/v1Tapscripts" } } }, @@ -824,7 +830,7 @@ "title": "VTXO outpoint signed with script's secret key" } }, - "description": "This message is used to prove to the ASP that the user controls the vtxo without revealing the whole VTXO descriptor." + "description": "This message is used to prove to the ASP that the user controls the vtxo without revealing the whole VTXO taproot tree." }, "v1PingResponse": { "type": "object" @@ -1137,6 +1143,17 @@ "v1SubmitTreeSignaturesResponse": { "type": "object" }, + "v1Tapscripts": { + "type": "object", + "properties": { + "scripts": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "v1Tree": { "type": "object", "properties": { diff --git a/api-spec/protobuf/ark/v1/service.proto b/api-spec/protobuf/ark/v1/service.proto index 9143513..84f6446 100755 --- a/api-spec/protobuf/ark/v1/service.proto +++ b/api-spec/protobuf/ark/v1/service.proto @@ -133,7 +133,10 @@ message GetBoardingAddressRequest { } message GetBoardingAddressResponse { string address = 1; - string descriptor = 2; + oneof taproot_tree { + string descriptor = 2; + Tapscripts tapscripts = 3; + } } /* In-Round Payment API messages */ @@ -298,7 +301,10 @@ message Outpoint { message Input { Outpoint outpoint = 1; - string descriptor = 2; + oneof taproot_tree { + string descriptor = 2; + Tapscripts tapscripts = 3; + } } message Output { @@ -355,7 +361,7 @@ message RedeemTransaction { repeated Vtxo spendable_vtxos = 3; } -// This message is used to prove to the ASP that the user controls the vtxo without revealing the whole VTXO descriptor. +// This message is used to prove to the ASP that the user controls the vtxo without revealing the whole VTXO taproot tree. message OwnershipProof { string control_block = 1; string script = 2; @@ -378,4 +384,8 @@ message DeleteNostrRecipientRequest { repeated SignedVtxoOutpoint vtxos = 1; } -message DeleteNostrRecipientResponse {} \ No newline at end of file +message DeleteNostrRecipientResponse {} + +message Tapscripts { + repeated string scripts = 1; +} \ No newline at end of file diff --git a/api-spec/protobuf/gen/ark/v1/service.pb.go b/api-spec/protobuf/gen/ark/v1/service.pb.go index 20481c5..a6e96e6 100644 --- a/api-spec/protobuf/gen/ark/v1/service.pb.go +++ b/api-spec/protobuf/gen/ark/v1/service.pb.go @@ -277,8 +277,12 @@ type GetBoardingAddressResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` - Descriptor_ string `protobuf:"bytes,2,opt,name=descriptor,proto3" json:"descriptor,omitempty"` + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + // Types that are assignable to TaprootTree: + // + // *GetBoardingAddressResponse_Descriptor_ + // *GetBoardingAddressResponse_Tapscripts + TaprootTree isGetBoardingAddressResponse_TaprootTree `protobuf_oneof:"taproot_tree"` } func (x *GetBoardingAddressResponse) Reset() { @@ -320,13 +324,43 @@ func (x *GetBoardingAddressResponse) GetAddress() string { return "" } +func (m *GetBoardingAddressResponse) GetTaprootTree() isGetBoardingAddressResponse_TaprootTree { + if m != nil { + return m.TaprootTree + } + return nil +} + func (x *GetBoardingAddressResponse) GetDescriptor_() string { - if x != nil { + if x, ok := x.GetTaprootTree().(*GetBoardingAddressResponse_Descriptor_); ok { return x.Descriptor_ } return "" } +func (x *GetBoardingAddressResponse) GetTapscripts() *Tapscripts { + if x, ok := x.GetTaprootTree().(*GetBoardingAddressResponse_Tapscripts); ok { + return x.Tapscripts + } + return nil +} + +type isGetBoardingAddressResponse_TaprootTree interface { + isGetBoardingAddressResponse_TaprootTree() +} + +type GetBoardingAddressResponse_Descriptor_ struct { + Descriptor_ string `protobuf:"bytes,2,opt,name=descriptor,proto3,oneof"` +} + +type GetBoardingAddressResponse_Tapscripts struct { + Tapscripts *Tapscripts `protobuf:"bytes,3,opt,name=tapscripts,proto3,oneof"` +} + +func (*GetBoardingAddressResponse_Descriptor_) isGetBoardingAddressResponse_TaprootTree() {} + +func (*GetBoardingAddressResponse_Tapscripts) isGetBoardingAddressResponse_TaprootTree() {} + type RegisterInputsForNextRoundRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2086,8 +2120,12 @@ type Input struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Outpoint *Outpoint `protobuf:"bytes,1,opt,name=outpoint,proto3" json:"outpoint,omitempty"` - Descriptor_ string `protobuf:"bytes,2,opt,name=descriptor,proto3" json:"descriptor,omitempty"` + Outpoint *Outpoint `protobuf:"bytes,1,opt,name=outpoint,proto3" json:"outpoint,omitempty"` + // Types that are assignable to TaprootTree: + // + // *Input_Descriptor_ + // *Input_Tapscripts + TaprootTree isInput_TaprootTree `protobuf_oneof:"taproot_tree"` } func (x *Input) Reset() { @@ -2129,13 +2167,43 @@ func (x *Input) GetOutpoint() *Outpoint { return nil } +func (m *Input) GetTaprootTree() isInput_TaprootTree { + if m != nil { + return m.TaprootTree + } + return nil +} + func (x *Input) GetDescriptor_() string { - if x != nil { + if x, ok := x.GetTaprootTree().(*Input_Descriptor_); ok { return x.Descriptor_ } return "" } +func (x *Input) GetTapscripts() *Tapscripts { + if x, ok := x.GetTaprootTree().(*Input_Tapscripts); ok { + return x.Tapscripts + } + return nil +} + +type isInput_TaprootTree interface { + isInput_TaprootTree() +} + +type Input_Descriptor_ struct { + Descriptor_ string `protobuf:"bytes,2,opt,name=descriptor,proto3,oneof"` +} + +type Input_Tapscripts struct { + Tapscripts *Tapscripts `protobuf:"bytes,3,opt,name=tapscripts,proto3,oneof"` +} + +func (*Input_Descriptor_) isInput_TaprootTree() {} + +func (*Input_Tapscripts) isInput_TaprootTree() {} + type Output struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2728,7 +2796,7 @@ func (x *RedeemTransaction) GetSpendableVtxos() []*Vtxo { return nil } -// This message is used to prove to the ASP that the user controls the vtxo without revealing the whole VTXO descriptor. +// This message is used to prove to the ASP that the user controls the vtxo without revealing the whole VTXO taproot tree. type OwnershipProof struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -3025,6 +3093,53 @@ func (*DeleteNostrRecipientResponse) Descriptor() ([]byte, []int) { return file_ark_v1_service_proto_rawDescGZIP(), []int{51} } +type Tapscripts struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Scripts []string `protobuf:"bytes,1,rep,name=scripts,proto3" json:"scripts,omitempty"` +} + +func (x *Tapscripts) Reset() { + *x = Tapscripts{} + if protoimpl.UnsafeEnabled { + mi := &file_ark_v1_service_proto_msgTypes[52] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Tapscripts) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Tapscripts) ProtoMessage() {} + +func (x *Tapscripts) ProtoReflect() protoreflect.Message { + mi := &file_ark_v1_service_proto_msgTypes[52] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Tapscripts.ProtoReflect.Descriptor instead. +func (*Tapscripts) Descriptor() ([]byte, []int) { + return file_ark_v1_service_proto_rawDescGZIP(), []int{52} +} + +func (x *Tapscripts) GetScripts() []string { + if x != nil { + return x.Scripts + } + return nil +} + var File_ark_v1_service_proto protoreflect.FileDescriptor var file_ark_v1_service_proto_rawDesc = []byte{ @@ -3060,448 +3175,460 @@ var file_ark_v1_service_proto_rawDesc = []byte{ 0x73, 0x22, 0x33, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x22, 0x56, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x42, 0x6f, 0x61, - 0x72, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1e, - 0x0a, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x22, 0xa5, - 0x01, 0x0a, 0x21, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x70, 0x75, 0x74, - 0x73, 0x46, 0x6f, 0x72, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, - 0x70, 0x75, 0x74, 0x52, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x2e, 0x0a, 0x10, 0x65, - 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0f, 0x65, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, - 0x61, 0x6c, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x88, 0x01, 0x01, 0x12, 0x14, 0x0a, 0x05, 0x6e, - 0x6f, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x6f, 0x74, 0x65, - 0x73, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x65, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x5f, - 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x22, 0x34, 0x0a, 0x22, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, - 0x65, 0x72, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x46, 0x6f, 0x72, 0x4e, 0x65, 0x78, 0x74, 0x52, - 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x5e, 0x0a, 0x22, - 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x46, - 0x6f, 0x72, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, - 0x69, 0x64, 0x12, 0x28, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, 0x74, - 0x70, 0x75, 0x74, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x22, 0x25, 0x0a, 0x23, - 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x46, - 0x6f, 0x72, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x6d, 0x0a, 0x17, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x54, 0x72, 0x65, - 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, - 0x0a, 0x08, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, - 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, - 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, - 0x65, 0x73, 0x22, 0x1a, 0x0a, 0x18, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x54, 0x72, 0x65, 0x65, - 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x79, - 0x0a, 0x1b, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x54, 0x72, 0x65, 0x65, 0x53, 0x69, 0x67, 0x6e, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, - 0x08, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, - 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, - 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x74, 0x72, 0x65, 0x65, 0x53, - 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x22, 0x1e, 0x0a, 0x1c, 0x53, 0x75, 0x62, - 0x6d, 0x69, 0x74, 0x54, 0x72, 0x65, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x8e, 0x01, 0x0a, 0x1d, 0x53, 0x75, - 0x62, 0x6d, 0x69, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x66, 0x65, 0x69, - 0x74, 0x54, 0x78, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x73, - 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x66, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x5f, 0x74, 0x78, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x46, - 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, 0x12, 0x2b, 0x0a, 0x0f, 0x73, 0x69, 0x67, - 0x6e, 0x65, 0x64, 0x5f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x48, 0x00, 0x52, 0x0d, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x52, 0x6f, 0x75, 0x6e, - 0x64, 0x54, 0x78, 0x88, 0x01, 0x01, 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x65, - 0x64, 0x5f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x22, 0x20, 0x0a, 0x1e, 0x53, 0x75, - 0x62, 0x6d, 0x69, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x66, 0x65, 0x69, - 0x74, 0x54, 0x78, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x17, 0x0a, 0x15, - 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa7, 0x03, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x4f, 0x0a, 0x12, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, - 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x61, - 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x69, 0x6e, 0x61, 0x6c, - 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x11, - 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x46, 0x0a, 0x0f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, - 0x69, 0x7a, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x72, 0x6b, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, - 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x72, 0x6f, 0x75, 0x6e, 0x64, - 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x38, 0x0a, 0x0c, 0x72, 0x6f, 0x75, - 0x6e, 0x64, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x13, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x61, - 0x69, 0x6c, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0b, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x61, 0x69, - 0x6c, 0x65, 0x64, 0x12, 0x40, 0x0a, 0x0d, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x73, 0x69, 0x67, - 0x6e, 0x69, 0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x72, 0x6b, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x69, - 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x6f, 0x0a, 0x1e, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x73, - 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x5f, 0x67, 0x65, - 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, - 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x69, 0x67, 0x6e, - 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, - 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x1b, 0x72, 0x6f, 0x75, 0x6e, 0x64, - 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x47, 0x65, 0x6e, - 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, - 0x2c, 0x0a, 0x0b, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, - 0x0a, 0x0a, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x0e, 0x0a, - 0x0c, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x64, 0x0a, - 0x11, 0x41, 0x73, 0x79, 0x6e, 0x63, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x70, - 0x75, 0x74, 0x12, 0x23, 0x0a, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74, - 0x52, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x66, 0x6f, 0x72, 0x66, 0x65, - 0x69, 0x74, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0f, 0x66, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x4c, 0x65, 0x61, 0x66, 0x48, - 0x61, 0x73, 0x68, 0x22, 0x73, 0x0a, 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x06, 0x69, - 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x72, - 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x79, 0x6e, 0x63, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x28, - 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x0e, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, - 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x22, 0x41, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x64, 0x65, - 0x65, 0x6d, 0x5f, 0x74, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x69, 0x67, - 0x6e, 0x65, 0x64, 0x52, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x54, 0x78, 0x22, 0x42, 0x0a, 0x16, 0x43, - 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, - 0x72, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x5f, 0x74, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x52, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x54, 0x78, 0x22, - 0x19, 0x0a, 0x17, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x25, 0x0a, 0x0f, 0x47, 0x65, - 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, - 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, - 0x64, 0x22, 0x37, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, - 0x75, 0x6e, 0x64, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x25, 0x0a, 0x13, 0x47, 0x65, - 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x22, 0x9e, 0x01, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x42, 0x6f, + 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, + 0x20, 0x0a, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, + 0x72, 0x12, 0x34, 0x0a, 0x0a, 0x74, 0x61, 0x70, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x54, + 0x61, 0x70, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x48, 0x00, 0x52, 0x0a, 0x74, 0x61, 0x70, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x42, 0x0e, 0x0a, 0x0c, 0x74, 0x61, 0x70, 0x72, 0x6f, + 0x6f, 0x74, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x22, 0xa5, 0x01, 0x0a, 0x21, 0x52, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x46, 0x6f, 0x72, 0x4e, 0x65, 0x78, + 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, + 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, + 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x06, 0x69, 0x6e, + 0x70, 0x75, 0x74, 0x73, 0x12, 0x2e, 0x0a, 0x10, 0x65, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, + 0x6c, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, + 0x52, 0x0f, 0x65, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x50, 0x75, 0x62, 0x6b, 0x65, + 0x79, 0x88, 0x01, 0x01, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x6f, 0x74, 0x65, 0x73, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x65, + 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x22, + 0x34, 0x0a, 0x22, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x70, 0x75, 0x74, + 0x73, 0x46, 0x6f, 0x72, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x5e, 0x0a, 0x22, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, + 0x72, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x46, 0x6f, 0x72, 0x4e, 0x65, 0x78, 0x74, 0x52, + 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x28, 0x0a, 0x07, 0x6f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, + 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x07, 0x6f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x73, 0x22, 0x25, 0x0a, 0x23, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, + 0x72, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x46, 0x6f, 0x72, 0x4e, 0x65, 0x78, 0x74, 0x52, + 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x6d, 0x0a, 0x17, + 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x75, 0x6e, 0x64, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x6f, 0x75, 0x6e, 0x64, + 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x72, + 0x65, 0x65, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x74, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x22, 0x1a, 0x0a, 0x18, 0x53, + 0x75, 0x62, 0x6d, 0x69, 0x74, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x79, 0x0a, 0x1b, 0x53, 0x75, 0x62, 0x6d, 0x69, + 0x74, 0x54, 0x72, 0x65, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x49, + 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x72, 0x65, + 0x65, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0e, 0x74, 0x72, 0x65, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x73, 0x22, 0x1e, 0x0a, 0x1c, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x54, 0x72, 0x65, 0x65, + 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x8e, 0x01, 0x0a, 0x1d, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x53, 0x69, 0x67, + 0x6e, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x66, + 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x5f, 0x74, 0x78, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x10, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x54, + 0x78, 0x73, 0x12, 0x2b, 0x0a, 0x0f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x72, 0x6f, 0x75, + 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0d, 0x73, + 0x69, 0x67, 0x6e, 0x65, 0x64, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x88, 0x01, 0x01, 0x42, + 0x12, 0x0a, 0x10, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x72, 0x6f, 0x75, 0x6e, 0x64, + 0x5f, 0x74, 0x78, 0x22, 0x20, 0x0a, 0x1e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x53, 0x69, 0x67, + 0x6e, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x17, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa7, + 0x03, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x12, 0x72, 0x6f, 0x75, + 0x6e, 0x64, 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x11, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x69, + 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x0f, 0x72, 0x6f, + 0x75, 0x6e, 0x64, 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, + 0x6e, 0x64, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x48, 0x00, 0x52, 0x0e, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, + 0x65, 0x64, 0x12, 0x38, 0x0a, 0x0c, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x66, 0x61, 0x69, 0x6c, + 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x48, 0x00, 0x52, + 0x0b, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x40, 0x0a, 0x0d, + 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, + 0x6e, 0x64, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, + 0x52, 0x0c, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x6f, + 0x0a, 0x1e, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x5f, + 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x6e, 0x63, + 0x65, 0x73, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x48, 0x00, 0x52, 0x1b, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, + 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x42, + 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x2c, 0x0a, 0x0b, 0x50, 0x69, 0x6e, 0x67, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x0e, 0x0a, 0x0c, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x64, 0x0a, 0x11, 0x41, 0x73, 0x79, 0x6e, 0x63, 0x50, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x23, 0x0a, 0x05, 0x69, + 0x6e, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x72, 0x6b, + 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, + 0x12, 0x2a, 0x0a, 0x11, 0x66, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x5f, 0x6c, 0x65, 0x61, 0x66, + 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x66, 0x6f, 0x72, + 0x66, 0x65, 0x69, 0x74, 0x4c, 0x65, 0x61, 0x66, 0x48, 0x61, 0x73, 0x68, 0x22, 0x73, 0x0a, 0x14, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, + 0x79, 0x6e, 0x63, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, + 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x28, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, + 0x31, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x73, 0x22, 0x41, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x69, + 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x5f, 0x74, 0x78, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x52, 0x65, 0x64, 0x65, + 0x65, 0x6d, 0x54, 0x78, 0x22, 0x42, 0x0a, 0x16, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, + 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, + 0x0a, 0x10, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x5f, + 0x74, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, + 0x52, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x54, 0x78, 0x22, 0x19, 0x0a, 0x17, 0x43, 0x6f, 0x6d, 0x70, + 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x25, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x22, 0x37, 0x0a, 0x10, 0x47, 0x65, + 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, + 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, + 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x05, 0x72, 0x6f, + 0x75, 0x6e, 0x64, 0x22, 0x25, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x42, + 0x79, 0x49, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x3b, 0x0a, 0x14, 0x47, 0x65, + 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x23, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, + 0x52, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x2c, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x56, + 0x74, 0x78, 0x6f, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x79, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x74, 0x78, + 0x6f, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x0f, 0x73, 0x70, + 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x74, 0x78, + 0x6f, 0x52, 0x0e, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x74, 0x78, 0x6f, + 0x73, 0x12, 0x2d, 0x0a, 0x0b, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x5f, 0x76, 0x74, 0x78, 0x6f, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, + 0x56, 0x74, 0x78, 0x6f, 0x52, 0x0a, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, + 0x22, 0xbb, 0x01, 0x0a, 0x16, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x72, + 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, + 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x12, 0x29, 0x0a, 0x09, 0x76, 0x74, 0x78, 0x6f, 0x5f, 0x74, + 0x72, 0x65, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, + 0x76, 0x31, 0x2e, 0x54, 0x72, 0x65, 0x65, 0x52, 0x08, 0x76, 0x74, 0x78, 0x6f, 0x54, 0x72, 0x65, + 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, + 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x73, 0x12, 0x2b, 0x0a, 0x12, 0x6d, 0x69, 0x6e, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x66, + 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6d, + 0x69, 0x6e, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x46, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x22, 0x44, + 0x0a, 0x13, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, + 0x78, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x6f, 0x75, 0x6e, 0x64, + 0x54, 0x78, 0x69, 0x64, 0x22, 0x35, 0x0a, 0x0b, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x61, 0x69, + 0x6c, 0x65, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xb8, 0x01, 0x0a, 0x11, + 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, - 0x64, 0x22, 0x3b, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x42, 0x79, 0x49, - 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x05, 0x72, 0x6f, 0x75, - 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x2c, - 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x79, 0x0a, 0x11, - 0x4c, 0x69, 0x73, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x35, 0x0a, 0x0f, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, - 0x74, 0x78, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, 0x6b, - 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x74, 0x78, 0x6f, 0x52, 0x0e, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x61, - 0x62, 0x6c, 0x65, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x12, 0x2d, 0x0a, 0x0b, 0x73, 0x70, 0x65, 0x6e, - 0x74, 0x5f, 0x76, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, - 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x74, 0x78, 0x6f, 0x52, 0x0a, 0x73, 0x70, 0x65, - 0x6e, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x22, 0xbb, 0x01, 0x0a, 0x16, 0x52, 0x6f, 0x75, 0x6e, - 0x64, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, - 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x12, 0x29, 0x0a, - 0x09, 0x76, 0x74, 0x78, 0x6f, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x0c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x65, 0x65, 0x52, 0x08, - 0x76, 0x74, 0x78, 0x6f, 0x54, 0x72, 0x65, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x2b, 0x0a, 0x12, 0x6d, 0x69, 0x6e, 0x5f, - 0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x46, 0x65, - 0x65, 0x52, 0x61, 0x74, 0x65, 0x22, 0x44, 0x0a, 0x13, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x69, - 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, - 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x69, 0x64, 0x22, 0x35, 0x0a, 0x0b, 0x52, - 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, - 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, - 0x6f, 0x6e, 0x22, 0xb8, 0x01, 0x0a, 0x11, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x69, 0x67, 0x6e, - 0x69, 0x6e, 0x67, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x6f, 0x73, 0x69, - 0x67, 0x6e, 0x65, 0x72, 0x73, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x10, 0x63, 0x6f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x73, 0x50, 0x75, - 0x62, 0x6b, 0x65, 0x79, 0x73, 0x12, 0x3a, 0x0a, 0x12, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, - 0x64, 0x5f, 0x76, 0x74, 0x78, 0x6f, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x65, 0x65, 0x52, - 0x10, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x56, 0x74, 0x78, 0x6f, 0x54, 0x72, 0x65, - 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x72, 0x6f, - 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x75, 0x6e, - 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x22, 0x53, 0x0a, - 0x20, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x6e, - 0x63, 0x65, 0x73, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, - 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, - 0x65, 0x73, 0x22, 0xf0, 0x01, 0x0a, 0x05, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x0e, 0x0a, 0x02, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, - 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x03, 0x65, 0x6e, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x12, - 0x29, 0x0a, 0x09, 0x76, 0x74, 0x78, 0x6f, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x65, 0x65, - 0x52, 0x08, 0x76, 0x74, 0x78, 0x6f, 0x54, 0x72, 0x65, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x6f, - 0x72, 0x66, 0x65, 0x69, 0x74, 0x5f, 0x74, 0x78, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x0a, 0x66, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x63, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x28, 0x0a, 0x05, 0x73, - 0x74, 0x61, 0x67, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x61, 0x72, 0x6b, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x05, - 0x73, 0x74, 0x61, 0x67, 0x65, 0x22, 0x32, 0x0a, 0x08, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x76, 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x04, 0x76, 0x6f, 0x75, 0x74, 0x22, 0x55, 0x0a, 0x05, 0x49, 0x6e, 0x70, - 0x75, 0x74, 0x12, 0x2c, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, - 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, - 0x22, 0x3a, 0x0a, 0x06, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x31, 0x0a, 0x04, - 0x54, 0x72, 0x65, 0x65, 0x12, 0x29, 0x0a, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, - 0x65, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x22, - 0x2f, 0x0a, 0x09, 0x54, 0x72, 0x65, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x22, 0x0a, 0x05, - 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, - 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x05, 0x6e, 0x6f, 0x64, 0x65, 0x73, - 0x22, 0x4b, 0x0a, 0x04, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x0e, 0x0a, 0x02, - 0x74, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x74, 0x78, 0x12, 0x1f, 0x0a, 0x0b, - 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x54, 0x78, 0x69, 0x64, 0x22, 0xc2, 0x02, - 0x0a, 0x04, 0x56, 0x74, 0x78, 0x6f, 0x12, 0x2c, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, - 0x31, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, - 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x05, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x6f, - 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x70, 0x65, - 0x6e, 0x74, 0x5f, 0x62, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x70, 0x65, - 0x6e, 0x74, 0x42, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x5f, 0x61, - 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, - 0x74, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x77, 0x65, 0x70, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x05, 0x73, 0x77, 0x65, 0x70, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x70, 0x65, - 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x50, - 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x64, 0x65, 0x65, 0x6d, - 0x5f, 0x74, 0x78, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x64, 0x65, 0x65, - 0x6d, 0x54, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, - 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, - 0x6b, 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, - 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, - 0x41, 0x74, 0x22, 0x1e, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x22, 0x8c, 0x01, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, - 0x6e, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, - 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x33, 0x0a, 0x06, 0x72, 0x65, 0x64, 0x65, 0x65, 0x6d, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x48, 0x00, 0x52, 0x06, 0x72, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x42, 0x04, 0x0a, 0x02, 0x74, - 0x78, 0x22, 0xd8, 0x01, 0x0a, 0x10, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x31, 0x0a, 0x0b, 0x73, 0x70, - 0x65, 0x6e, 0x74, 0x5f, 0x76, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x10, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x52, 0x0a, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x12, 0x35, 0x0a, - 0x0f, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x74, 0x78, 0x6f, 0x73, - 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, - 0x56, 0x74, 0x78, 0x6f, 0x52, 0x0e, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x56, - 0x74, 0x78, 0x6f, 0x73, 0x12, 0x46, 0x0a, 0x16, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x5f, - 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x04, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, - 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x14, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x42, - 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x22, 0x91, 0x01, 0x0a, - 0x11, 0x52, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x31, 0x0a, 0x0b, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x5f, - 0x76, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x72, - 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0a, 0x73, - 0x70, 0x65, 0x6e, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x12, 0x35, 0x0a, 0x0f, 0x73, 0x70, 0x65, - 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x74, 0x78, 0x6f, - 0x52, 0x0e, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x74, 0x78, 0x6f, 0x73, - 0x22, 0x6b, 0x0a, 0x0e, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x50, 0x72, 0x6f, - 0x6f, 0x66, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x5f, 0x62, 0x6c, - 0x6f, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x72, - 0x6f, 0x6c, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, - 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x70, 0x0a, - 0x12, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x56, 0x74, 0x78, 0x6f, 0x4f, 0x75, 0x74, 0x70, 0x6f, - 0x69, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4f, - 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x12, 0x2c, 0x0a, 0x05, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x16, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x73, - 0x68, 0x69, 0x70, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x05, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x22, - 0x75, 0x0a, 0x18, 0x53, 0x65, 0x74, 0x4e, 0x6f, 0x73, 0x74, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, - 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x6e, - 0x6f, 0x73, 0x74, 0x72, 0x5f, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x6f, 0x73, 0x74, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, - 0x69, 0x65, 0x6e, 0x74, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x02, 0x20, + 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x6f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x73, 0x5f, 0x70, + 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x63, 0x6f, + 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x73, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x12, 0x3a, + 0x0a, 0x12, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x76, 0x74, 0x78, 0x6f, 0x5f, + 0x74, 0x72, 0x65, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, 0x6b, + 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x65, 0x65, 0x52, 0x10, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, + 0x65, 0x64, 0x56, 0x74, 0x78, 0x6f, 0x54, 0x72, 0x65, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x75, 0x6e, + 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x52, + 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x22, 0x53, 0x0a, 0x20, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, + 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x47, 0x65, 0x6e, 0x65, + 0x72, 0x61, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x72, + 0x65, 0x65, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x74, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x22, 0xf0, 0x01, 0x0a, 0x05, + 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, + 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x19, 0x0a, + 0x08, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x12, 0x29, 0x0a, 0x09, 0x76, 0x74, 0x78, 0x6f, + 0x5f, 0x74, 0x72, 0x65, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, + 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x65, 0x65, 0x52, 0x08, 0x76, 0x74, 0x78, 0x6f, 0x54, + 0x72, 0x65, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x5f, 0x74, + 0x78, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x66, 0x6f, 0x72, 0x66, 0x65, 0x69, + 0x74, 0x54, 0x78, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x73, 0x12, 0x28, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, + 0x6e, 0x64, 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x22, 0x32, + 0x0a, 0x08, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x12, + 0x0a, 0x04, 0x76, 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x76, 0x6f, + 0x75, 0x74, 0x22, 0x9d, 0x01, 0x0a, 0x05, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x2c, 0x0a, 0x08, + 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, + 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x20, 0x0a, 0x0a, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, + 0x52, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x34, 0x0a, 0x0a, + 0x74, 0x61, 0x70, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x12, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x70, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x73, 0x48, 0x00, 0x52, 0x0a, 0x74, 0x61, 0x70, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x73, 0x42, 0x0e, 0x0a, 0x0c, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x74, 0x72, + 0x65, 0x65, 0x22, 0x3a, 0x0a, 0x06, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x18, 0x0a, 0x07, + 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x31, + 0x0a, 0x04, 0x54, 0x72, 0x65, 0x65, 0x12, 0x29, 0x0a, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, + 0x54, 0x72, 0x65, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, + 0x73, 0x22, 0x2f, 0x0a, 0x09, 0x54, 0x72, 0x65, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x22, + 0x0a, 0x05, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, + 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x05, 0x6e, 0x6f, 0x64, + 0x65, 0x73, 0x22, 0x4b, 0x0a, 0x04, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x0e, + 0x0a, 0x02, 0x74, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x74, 0x78, 0x12, 0x1f, + 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x54, 0x78, 0x69, 0x64, 0x22, + 0xc2, 0x02, 0x0a, 0x04, 0x56, 0x74, 0x78, 0x6f, 0x12, 0x2c, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x72, 0x6b, + 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, + 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, + 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, + 0x70, 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, + 0x70, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, + 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x65, 0x78, 0x70, 0x69, 0x72, + 0x65, 0x41, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x77, 0x65, 0x70, 0x74, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x05, 0x73, 0x77, 0x65, 0x70, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, + 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, + 0x73, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x64, 0x65, + 0x65, 0x6d, 0x5f, 0x74, 0x78, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x64, + 0x65, 0x65, 0x6d, 0x54, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, 0x0a, + 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, + 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, + 0x5f, 0x61, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x41, 0x74, 0x22, 0x1e, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x22, 0x8c, 0x01, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, + 0x00, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x33, 0x0a, 0x06, 0x72, 0x65, 0x64, 0x65, + 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x06, 0x72, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x42, 0x04, 0x0a, + 0x02, 0x74, 0x78, 0x22, 0xd8, 0x01, 0x0a, 0x10, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x31, 0x0a, 0x0b, + 0x73, 0x70, 0x65, 0x6e, 0x74, 0x5f, 0x76, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x52, 0x0a, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x12, + 0x35, 0x0a, 0x0f, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x74, 0x78, + 0x6f, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, + 0x31, 0x2e, 0x56, 0x74, 0x78, 0x6f, 0x52, 0x0e, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, + 0x65, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x12, 0x46, 0x0a, 0x16, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, + 0x64, 0x5f, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x75, 0x74, 0x78, 0x6f, 0x73, + 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, + 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x14, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, + 0x64, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x22, 0x91, + 0x01, 0x0a, 0x11, 0x52, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x31, 0x0a, 0x0b, 0x73, 0x70, 0x65, 0x6e, + 0x74, 0x5f, 0x76, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, + 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, + 0x0a, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x12, 0x35, 0x0a, 0x0f, 0x73, + 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x74, + 0x78, 0x6f, 0x52, 0x0e, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x74, 0x78, + 0x6f, 0x73, 0x22, 0x6b, 0x0a, 0x0e, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x50, + 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x5f, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, + 0x70, 0x0a, 0x12, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x56, 0x74, 0x78, 0x6f, 0x4f, 0x75, 0x74, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, + 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x05, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x77, 0x6e, 0x65, + 0x72, 0x73, 0x68, 0x69, 0x70, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x05, 0x70, 0x72, 0x6f, 0x6f, + 0x66, 0x22, 0x75, 0x0a, 0x18, 0x53, 0x65, 0x74, 0x4e, 0x6f, 0x73, 0x74, 0x72, 0x52, 0x65, 0x63, + 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, + 0x0f, 0x6e, 0x6f, 0x73, 0x74, 0x72, 0x5f, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x6f, 0x73, 0x74, 0x72, 0x52, 0x65, 0x63, + 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x74, 0x78, 0x6f, 0x73, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, + 0x69, 0x67, 0x6e, 0x65, 0x64, 0x56, 0x74, 0x78, 0x6f, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x52, 0x05, 0x76, 0x74, 0x78, 0x6f, 0x73, 0x22, 0x1b, 0x0a, 0x19, 0x53, 0x65, 0x74, 0x4e, + 0x6f, 0x73, 0x74, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4f, 0x0a, 0x1b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, + 0x6f, 0x73, 0x74, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x56, 0x74, 0x78, 0x6f, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, - 0x05, 0x76, 0x74, 0x78, 0x6f, 0x73, 0x22, 0x1b, 0x0a, 0x19, 0x53, 0x65, 0x74, 0x4e, 0x6f, 0x73, - 0x74, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x4f, 0x0a, 0x1b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x6f, 0x73, - 0x74, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x65, - 0x64, 0x56, 0x74, 0x78, 0x6f, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x05, 0x76, - 0x74, 0x78, 0x6f, 0x73, 0x22, 0x1e, 0x0a, 0x1c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x6f, - 0x73, 0x74, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x98, 0x01, 0x0a, 0x0a, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x74, - 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x53, 0x54, 0x41, - 0x47, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, - 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, 0x5f, - 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x01, 0x12, 0x1c, - 0x0a, 0x18, 0x52, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, 0x5f, 0x46, 0x49, - 0x4e, 0x41, 0x4c, 0x49, 0x5a, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15, - 0x52, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, 0x5f, 0x46, 0x49, 0x4e, 0x41, - 0x4c, 0x49, 0x5a, 0x45, 0x44, 0x10, 0x03, 0x12, 0x16, 0x0a, 0x12, 0x52, 0x4f, 0x55, 0x4e, 0x44, - 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x04, 0x32, - 0xe0, 0x0f, 0x0a, 0x0a, 0x41, 0x72, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4c, - 0x0a, 0x07, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x61, 0x72, 0x6b, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x17, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, - 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x10, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x0a, 0x12, 0x08, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x74, 0x0a, 0x12, - 0x47, 0x65, 0x74, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x12, 0x21, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, - 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, - 0x65, 0x74, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x17, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x11, 0x3a, 0x01, 0x2a, 0x22, 0x0c, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, - 0x6e, 0x67, 0x12, 0x98, 0x01, 0x0a, 0x1a, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, - 0x6e, 0x70, 0x75, 0x74, 0x73, 0x46, 0x6f, 0x72, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x6f, 0x75, 0x6e, - 0x64, 0x12, 0x29, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, - 0x74, 0x65, 0x72, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x46, 0x6f, 0x72, 0x4e, 0x65, 0x78, 0x74, - 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x61, - 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, - 0x70, 0x75, 0x74, 0x73, 0x46, 0x6f, 0x72, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x23, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, - 0x3a, 0x01, 0x2a, 0x22, 0x18, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x72, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x9c, 0x01, - 0x0a, 0x1b, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, - 0x73, 0x46, 0x6f, 0x72, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x2a, 0x2e, - 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, - 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x46, 0x6f, 0x72, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x6f, 0x75, - 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x61, 0x72, 0x6b, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x70, 0x75, - 0x74, 0x73, 0x46, 0x6f, 0x72, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x3a, 0x01, - 0x2a, 0x22, 0x19, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x72, 0x65, 0x67, - 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x7d, 0x0a, 0x10, - 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, - 0x12, 0x1f, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, - 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x20, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, - 0x74, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x3a, 0x01, 0x2a, 0x22, 0x1b, - 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x74, 0x72, 0x65, 0x65, 0x2f, 0x73, - 0x75, 0x62, 0x6d, 0x69, 0x74, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x8d, 0x01, 0x0a, 0x14, - 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x54, 0x72, 0x65, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x73, 0x12, 0x23, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, - 0x62, 0x6d, 0x69, 0x74, 0x54, 0x72, 0x65, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x61, 0x72, 0x6b, 0x2e, - 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x54, 0x72, 0x65, 0x65, 0x53, 0x69, 0x67, - 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x2a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x24, 0x3a, 0x01, 0x2a, 0x22, 0x1f, 0x2f, 0x76, 0x31, 0x2f, - 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x74, 0x72, 0x65, 0x65, 0x2f, 0x73, 0x75, 0x62, 0x6d, 0x69, - 0x74, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x8e, 0x01, 0x0a, 0x16, - 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x66, - 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, 0x12, 0x25, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, - 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x66, - 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, - 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x53, 0x69, 0x67, - 0x6e, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x3a, 0x01, 0x2a, - 0x22, 0x1a, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x73, 0x75, 0x62, 0x6d, - 0x69, 0x74, 0x46, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, 0x12, 0x65, 0x0a, 0x0e, - 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x1d, - 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, - 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, - 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x12, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x0c, 0x12, 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, - 0x73, 0x30, 0x01, 0x12, 0x56, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x13, 0x2e, 0x61, 0x72, - 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x14, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x23, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x12, 0x1b, - 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x70, 0x69, 0x6e, 0x67, 0x2f, 0x7b, - 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x64, 0x0a, 0x0d, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1c, 0x2e, 0x61, - 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, - 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, 0x72, 0x6b, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x10, 0x3a, 0x01, 0x2a, 0x22, 0x0b, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x12, 0x73, 0x0a, 0x0f, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1e, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, - 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, - 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, - 0x22, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x6f, - 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x57, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, - 0x6e, 0x64, 0x12, 0x17, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, - 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x61, 0x72, - 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x12, 0x10, 0x2f, - 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x7b, 0x74, 0x78, 0x69, 0x64, 0x7d, 0x12, - 0x64, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x42, 0x79, 0x49, 0x64, 0x12, - 0x1b, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, - 0x64, 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x61, - 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x42, 0x79, - 0x49, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x13, 0x12, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x69, 0x64, - 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x5d, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x74, 0x78, - 0x6f, 0x73, 0x12, 0x18, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x56, 0x74, 0x78, 0x6f, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61, + 0x05, 0x76, 0x74, 0x78, 0x6f, 0x73, 0x22, 0x1e, 0x0a, 0x1c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x4e, 0x6f, 0x73, 0x74, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x26, 0x0a, 0x0a, 0x54, 0x61, 0x70, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x2a, 0x98, + 0x01, 0x0a, 0x0a, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, + 0x17, 0x52, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, 0x5f, 0x55, 0x4e, 0x53, + 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x4f, + 0x55, 0x4e, 0x44, 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, 0x5f, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, + 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x4f, 0x55, 0x4e, + 0x44, 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, 0x5f, 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x49, 0x5a, 0x41, + 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15, 0x52, 0x4f, 0x55, 0x4e, 0x44, 0x5f, + 0x53, 0x54, 0x41, 0x47, 0x45, 0x5f, 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x49, 0x5a, 0x45, 0x44, 0x10, + 0x03, 0x12, 0x16, 0x0a, 0x12, 0x52, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, + 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x04, 0x32, 0xe0, 0x0f, 0x0a, 0x0a, 0x41, 0x72, + 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4c, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x49, + 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x61, 0x72, + 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x10, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0a, 0x12, 0x08, 0x2f, 0x76, + 0x31, 0x2f, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x74, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x42, 0x6f, 0x61, + 0x72, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x21, 0x2e, 0x61, + 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, + 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x22, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6f, 0x61, 0x72, + 0x64, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x17, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x3a, 0x01, 0x2a, 0x22, 0x0c, + 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x98, 0x01, 0x0a, + 0x1a, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x46, + 0x6f, 0x72, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x29, 0x2e, 0x61, 0x72, + 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x70, + 0x75, 0x74, 0x73, 0x46, 0x6f, 0x72, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x46, 0x6f, + 0x72, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x23, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x3a, 0x01, 0x2a, 0x22, 0x18, 0x2f, + 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, + 0x72, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x9c, 0x01, 0x0a, 0x1b, 0x52, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x46, 0x6f, 0x72, 0x4e, 0x65, + 0x78, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x2a, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, + 0x46, 0x6f, 0x72, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x46, 0x6f, 0x72, 0x4e, + 0x65, 0x78, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x3a, 0x01, 0x2a, 0x22, 0x19, 0x2f, 0x76, 0x31, + 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x7d, 0x0a, 0x10, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, + 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x1f, 0x2e, 0x61, 0x72, 0x6b, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, + 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x61, 0x72, + 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x54, 0x72, 0x65, 0x65, 0x4e, + 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x26, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x3a, 0x01, 0x2a, 0x22, 0x1b, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, + 0x75, 0x6e, 0x64, 0x2f, 0x74, 0x72, 0x65, 0x65, 0x2f, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x4e, + 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x8d, 0x01, 0x0a, 0x14, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, + 0x54, 0x72, 0x65, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x23, + 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x54, 0x72, + 0x65, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, + 0x6d, 0x69, 0x74, 0x54, 0x72, 0x65, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2a, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x24, 0x3a, 0x01, 0x2a, 0x22, 0x1f, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, + 0x74, 0x72, 0x65, 0x65, 0x2f, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x8e, 0x01, 0x0a, 0x16, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, + 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, + 0x12, 0x25, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, + 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, + 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x46, 0x6f, 0x72, + 0x66, 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x3a, 0x01, 0x2a, 0x22, 0x1a, 0x2f, 0x76, 0x31, 0x2f, + 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x46, 0x6f, 0x72, 0x66, + 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, 0x12, 0x65, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x1d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x12, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0c, 0x12, + 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x30, 0x01, 0x12, 0x56, 0x0a, + 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x13, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x50, + 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x61, 0x72, 0x6b, + 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x23, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x12, 0x1b, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, + 0x75, 0x6e, 0x64, 0x2f, 0x70, 0x69, 0x6e, 0x67, 0x2f, 0x7b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x64, 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x3a, 0x01, 0x2a, 0x22, 0x0b, + 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x73, 0x0a, 0x0f, 0x43, + 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1e, + 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, + 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, + 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, + 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, 0x2f, 0x76, 0x31, 0x2f, + 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, + 0x12, 0x57, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x17, 0x2e, 0x61, + 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, + 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x12, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, + 0x6e, 0x64, 0x2f, 0x7b, 0x74, 0x78, 0x69, 0x64, 0x7d, 0x12, 0x64, 0x0a, 0x0c, 0x47, 0x65, 0x74, + 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x42, 0x79, 0x49, 0x64, 0x12, 0x1b, 0x2e, 0x61, 0x72, 0x6b, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x42, 0x79, 0x49, 0x64, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, 0x76, + 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x69, 0x64, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, + 0x5d, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x12, 0x18, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, - 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x76, 0x74, 0x78, 0x6f, 0x73, 0x2f, 0x7b, 0x61, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x7d, 0x12, 0x80, 0x01, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, - 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x24, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x76, + 0x74, 0x78, 0x6f, 0x73, 0x2f, 0x7b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x7d, 0x12, 0x80, + 0x01, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x24, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, - 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x12, 0x12, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x30, 0x01, 0x12, 0x73, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x4e, 0x6f, - 0x73, 0x74, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x20, 0x2e, 0x61, - 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x4e, 0x6f, 0x73, 0x74, 0x72, 0x52, 0x65, - 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, - 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x4e, 0x6f, 0x73, 0x74, 0x72, - 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x3a, 0x01, 0x2a, 0x22, 0x0e, 0x2f, 0x76, - 0x31, 0x2f, 0x76, 0x74, 0x78, 0x6f, 0x2f, 0x6e, 0x6f, 0x73, 0x74, 0x72, 0x12, 0x83, 0x01, 0x0a, - 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x6f, 0x73, 0x74, 0x72, 0x52, 0x65, 0x63, 0x69, - 0x70, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x23, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x6f, 0x73, 0x74, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, - 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x61, 0x72, 0x6b, - 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x6f, 0x73, 0x74, 0x72, 0x52, - 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x3a, 0x01, 0x2a, 0x22, 0x15, 0x2f, 0x76, 0x31, - 0x2f, 0x76, 0x74, 0x78, 0x6f, 0x2f, 0x6e, 0x6f, 0x73, 0x74, 0x72, 0x2f, 0x64, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x42, 0x92, 0x01, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, - 0x31, 0x42, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, - 0x01, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x72, - 0x6b, 0x2d, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x61, 0x72, 0x6b, 0x2f, 0x61, 0x70, - 0x69, 0x2d, 0x73, 0x70, 0x65, 0x63, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, - 0x67, 0x65, 0x6e, 0x2f, 0x61, 0x72, 0x6b, 0x2f, 0x76, 0x31, 0x3b, 0x61, 0x72, 0x6b, 0x76, 0x31, - 0xa2, 0x02, 0x03, 0x41, 0x58, 0x58, 0xaa, 0x02, 0x06, 0x41, 0x72, 0x6b, 0x2e, 0x56, 0x31, 0xca, - 0x02, 0x06, 0x41, 0x72, 0x6b, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x12, 0x41, 0x72, 0x6b, 0x5c, 0x56, - 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x07, - 0x41, 0x72, 0x6b, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x12, 0x10, 0x2f, + 0x76, 0x31, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x30, + 0x01, 0x12, 0x73, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x4e, 0x6f, 0x73, 0x74, 0x72, 0x52, 0x65, 0x63, + 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x20, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x65, 0x74, 0x4e, 0x6f, 0x73, 0x74, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, + 0x31, 0x2e, 0x53, 0x65, 0x74, 0x4e, 0x6f, 0x73, 0x74, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x13, 0x3a, 0x01, 0x2a, 0x22, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x76, 0x74, 0x78, 0x6f, + 0x2f, 0x6e, 0x6f, 0x73, 0x74, 0x72, 0x12, 0x83, 0x01, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x4e, 0x6f, 0x73, 0x74, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x12, + 0x23, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, + 0x6f, 0x73, 0x74, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x6f, 0x73, 0x74, 0x72, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, + 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x1a, 0x3a, 0x01, 0x2a, 0x22, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x76, 0x74, 0x78, 0x6f, 0x2f, + 0x6e, 0x6f, 0x73, 0x74, 0x72, 0x2f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x92, 0x01, 0x0a, + 0x0a, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x42, 0x0c, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3d, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x72, 0x6b, 0x2d, 0x6e, 0x65, 0x74, 0x77, + 0x6f, 0x72, 0x6b, 0x2f, 0x61, 0x72, 0x6b, 0x2f, 0x61, 0x70, 0x69, 0x2d, 0x73, 0x70, 0x65, 0x63, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x61, 0x72, + 0x6b, 0x2f, 0x76, 0x31, 0x3b, 0x61, 0x72, 0x6b, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x41, 0x58, 0x58, + 0xaa, 0x02, 0x06, 0x41, 0x72, 0x6b, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x06, 0x41, 0x72, 0x6b, 0x5c, + 0x56, 0x31, 0xe2, 0x02, 0x12, 0x41, 0x72, 0x6b, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x07, 0x41, 0x72, 0x6b, 0x3a, 0x3a, 0x56, + 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -3517,7 +3644,7 @@ func file_ark_v1_service_proto_rawDescGZIP() []byte { } var file_ark_v1_service_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_ark_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 52) +var file_ark_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 53) var file_ark_v1_service_proto_goTypes = []interface{}{ (RoundStage)(0), // 0: ark.v1.RoundStage (*GetInfoRequest)(nil), // 1: ark.v1.GetInfoRequest @@ -3572,80 +3699,83 @@ var file_ark_v1_service_proto_goTypes = []interface{}{ (*SetNostrRecipientResponse)(nil), // 50: ark.v1.SetNostrRecipientResponse (*DeleteNostrRecipientRequest)(nil), // 51: ark.v1.DeleteNostrRecipientRequest (*DeleteNostrRecipientResponse)(nil), // 52: ark.v1.DeleteNostrRecipientResponse + (*Tapscripts)(nil), // 53: ark.v1.Tapscripts } var file_ark_v1_service_proto_depIdxs = []int32{ - 37, // 0: ark.v1.RegisterInputsForNextRoundRequest.inputs:type_name -> ark.v1.Input - 38, // 1: ark.v1.RegisterOutputsForNextRoundRequest.outputs:type_name -> ark.v1.Output - 30, // 2: ark.v1.GetEventStreamResponse.round_finalization:type_name -> ark.v1.RoundFinalizationEvent - 31, // 3: ark.v1.GetEventStreamResponse.round_finalized:type_name -> ark.v1.RoundFinalizedEvent - 32, // 4: ark.v1.GetEventStreamResponse.round_failed:type_name -> ark.v1.RoundFailed - 33, // 5: ark.v1.GetEventStreamResponse.round_signing:type_name -> ark.v1.RoundSigningEvent - 34, // 6: ark.v1.GetEventStreamResponse.round_signing_nonces_generated:type_name -> ark.v1.RoundSigningNoncesGeneratedEvent - 37, // 7: ark.v1.AsyncPaymentInput.input:type_name -> ark.v1.Input - 19, // 8: ark.v1.CreatePaymentRequest.inputs:type_name -> ark.v1.AsyncPaymentInput - 38, // 9: ark.v1.CreatePaymentRequest.outputs:type_name -> ark.v1.Output - 35, // 10: ark.v1.GetRoundResponse.round:type_name -> ark.v1.Round - 35, // 11: ark.v1.GetRoundByIdResponse.round:type_name -> ark.v1.Round - 42, // 12: ark.v1.ListVtxosResponse.spendable_vtxos:type_name -> ark.v1.Vtxo - 42, // 13: ark.v1.ListVtxosResponse.spent_vtxos:type_name -> ark.v1.Vtxo - 39, // 14: ark.v1.RoundFinalizationEvent.vtxo_tree:type_name -> ark.v1.Tree - 39, // 15: ark.v1.RoundSigningEvent.unsigned_vtxo_tree:type_name -> ark.v1.Tree - 39, // 16: ark.v1.Round.vtxo_tree:type_name -> ark.v1.Tree - 0, // 17: ark.v1.Round.stage:type_name -> ark.v1.RoundStage - 36, // 18: ark.v1.Input.outpoint:type_name -> ark.v1.Outpoint - 40, // 19: ark.v1.Tree.levels:type_name -> ark.v1.TreeLevel - 41, // 20: ark.v1.TreeLevel.nodes:type_name -> ark.v1.Node - 36, // 21: ark.v1.Vtxo.outpoint:type_name -> ark.v1.Outpoint - 45, // 22: ark.v1.GetTransactionsStreamResponse.round:type_name -> ark.v1.RoundTransaction - 46, // 23: ark.v1.GetTransactionsStreamResponse.redeem:type_name -> ark.v1.RedeemTransaction - 36, // 24: ark.v1.RoundTransaction.spent_vtxos:type_name -> ark.v1.Outpoint - 42, // 25: ark.v1.RoundTransaction.spendable_vtxos:type_name -> ark.v1.Vtxo - 36, // 26: ark.v1.RoundTransaction.claimed_boarding_utxos:type_name -> ark.v1.Outpoint - 36, // 27: ark.v1.RedeemTransaction.spent_vtxos:type_name -> ark.v1.Outpoint - 42, // 28: ark.v1.RedeemTransaction.spendable_vtxos:type_name -> ark.v1.Vtxo - 36, // 29: ark.v1.SignedVtxoOutpoint.outpoint:type_name -> ark.v1.Outpoint - 47, // 30: ark.v1.SignedVtxoOutpoint.proof:type_name -> ark.v1.OwnershipProof - 48, // 31: ark.v1.SetNostrRecipientRequest.vtxos:type_name -> ark.v1.SignedVtxoOutpoint - 48, // 32: ark.v1.DeleteNostrRecipientRequest.vtxos:type_name -> ark.v1.SignedVtxoOutpoint - 1, // 33: ark.v1.ArkService.GetInfo:input_type -> ark.v1.GetInfoRequest - 3, // 34: ark.v1.ArkService.GetBoardingAddress:input_type -> ark.v1.GetBoardingAddressRequest - 5, // 35: ark.v1.ArkService.RegisterInputsForNextRound:input_type -> ark.v1.RegisterInputsForNextRoundRequest - 7, // 36: ark.v1.ArkService.RegisterOutputsForNextRound:input_type -> ark.v1.RegisterOutputsForNextRoundRequest - 9, // 37: ark.v1.ArkService.SubmitTreeNonces:input_type -> ark.v1.SubmitTreeNoncesRequest - 11, // 38: ark.v1.ArkService.SubmitTreeSignatures:input_type -> ark.v1.SubmitTreeSignaturesRequest - 13, // 39: ark.v1.ArkService.SubmitSignedForfeitTxs:input_type -> ark.v1.SubmitSignedForfeitTxsRequest - 15, // 40: ark.v1.ArkService.GetEventStream:input_type -> ark.v1.GetEventStreamRequest - 17, // 41: ark.v1.ArkService.Ping:input_type -> ark.v1.PingRequest - 20, // 42: ark.v1.ArkService.CreatePayment:input_type -> ark.v1.CreatePaymentRequest - 22, // 43: ark.v1.ArkService.CompletePayment:input_type -> ark.v1.CompletePaymentRequest - 24, // 44: ark.v1.ArkService.GetRound:input_type -> ark.v1.GetRoundRequest - 26, // 45: ark.v1.ArkService.GetRoundById:input_type -> ark.v1.GetRoundByIdRequest - 28, // 46: ark.v1.ArkService.ListVtxos:input_type -> ark.v1.ListVtxosRequest - 43, // 47: ark.v1.ArkService.GetTransactionsStream:input_type -> ark.v1.GetTransactionsStreamRequest - 49, // 48: ark.v1.ArkService.SetNostrRecipient:input_type -> ark.v1.SetNostrRecipientRequest - 51, // 49: ark.v1.ArkService.DeleteNostrRecipient:input_type -> ark.v1.DeleteNostrRecipientRequest - 2, // 50: ark.v1.ArkService.GetInfo:output_type -> ark.v1.GetInfoResponse - 4, // 51: ark.v1.ArkService.GetBoardingAddress:output_type -> ark.v1.GetBoardingAddressResponse - 6, // 52: ark.v1.ArkService.RegisterInputsForNextRound:output_type -> ark.v1.RegisterInputsForNextRoundResponse - 8, // 53: ark.v1.ArkService.RegisterOutputsForNextRound:output_type -> ark.v1.RegisterOutputsForNextRoundResponse - 10, // 54: ark.v1.ArkService.SubmitTreeNonces:output_type -> ark.v1.SubmitTreeNoncesResponse - 12, // 55: ark.v1.ArkService.SubmitTreeSignatures:output_type -> ark.v1.SubmitTreeSignaturesResponse - 14, // 56: ark.v1.ArkService.SubmitSignedForfeitTxs:output_type -> ark.v1.SubmitSignedForfeitTxsResponse - 16, // 57: ark.v1.ArkService.GetEventStream:output_type -> ark.v1.GetEventStreamResponse - 18, // 58: ark.v1.ArkService.Ping:output_type -> ark.v1.PingResponse - 21, // 59: ark.v1.ArkService.CreatePayment:output_type -> ark.v1.CreatePaymentResponse - 23, // 60: ark.v1.ArkService.CompletePayment:output_type -> ark.v1.CompletePaymentResponse - 25, // 61: ark.v1.ArkService.GetRound:output_type -> ark.v1.GetRoundResponse - 27, // 62: ark.v1.ArkService.GetRoundById:output_type -> ark.v1.GetRoundByIdResponse - 29, // 63: ark.v1.ArkService.ListVtxos:output_type -> ark.v1.ListVtxosResponse - 44, // 64: ark.v1.ArkService.GetTransactionsStream:output_type -> ark.v1.GetTransactionsStreamResponse - 50, // 65: ark.v1.ArkService.SetNostrRecipient:output_type -> ark.v1.SetNostrRecipientResponse - 52, // 66: ark.v1.ArkService.DeleteNostrRecipient:output_type -> ark.v1.DeleteNostrRecipientResponse - 50, // [50:67] is the sub-list for method output_type - 33, // [33:50] is the sub-list for method input_type - 33, // [33:33] is the sub-list for extension type_name - 33, // [33:33] is the sub-list for extension extendee - 0, // [0:33] is the sub-list for field type_name + 53, // 0: ark.v1.GetBoardingAddressResponse.tapscripts:type_name -> ark.v1.Tapscripts + 37, // 1: ark.v1.RegisterInputsForNextRoundRequest.inputs:type_name -> ark.v1.Input + 38, // 2: ark.v1.RegisterOutputsForNextRoundRequest.outputs:type_name -> ark.v1.Output + 30, // 3: ark.v1.GetEventStreamResponse.round_finalization:type_name -> ark.v1.RoundFinalizationEvent + 31, // 4: ark.v1.GetEventStreamResponse.round_finalized:type_name -> ark.v1.RoundFinalizedEvent + 32, // 5: ark.v1.GetEventStreamResponse.round_failed:type_name -> ark.v1.RoundFailed + 33, // 6: ark.v1.GetEventStreamResponse.round_signing:type_name -> ark.v1.RoundSigningEvent + 34, // 7: ark.v1.GetEventStreamResponse.round_signing_nonces_generated:type_name -> ark.v1.RoundSigningNoncesGeneratedEvent + 37, // 8: ark.v1.AsyncPaymentInput.input:type_name -> ark.v1.Input + 19, // 9: ark.v1.CreatePaymentRequest.inputs:type_name -> ark.v1.AsyncPaymentInput + 38, // 10: ark.v1.CreatePaymentRequest.outputs:type_name -> ark.v1.Output + 35, // 11: ark.v1.GetRoundResponse.round:type_name -> ark.v1.Round + 35, // 12: ark.v1.GetRoundByIdResponse.round:type_name -> ark.v1.Round + 42, // 13: ark.v1.ListVtxosResponse.spendable_vtxos:type_name -> ark.v1.Vtxo + 42, // 14: ark.v1.ListVtxosResponse.spent_vtxos:type_name -> ark.v1.Vtxo + 39, // 15: ark.v1.RoundFinalizationEvent.vtxo_tree:type_name -> ark.v1.Tree + 39, // 16: ark.v1.RoundSigningEvent.unsigned_vtxo_tree:type_name -> ark.v1.Tree + 39, // 17: ark.v1.Round.vtxo_tree:type_name -> ark.v1.Tree + 0, // 18: ark.v1.Round.stage:type_name -> ark.v1.RoundStage + 36, // 19: ark.v1.Input.outpoint:type_name -> ark.v1.Outpoint + 53, // 20: ark.v1.Input.tapscripts:type_name -> ark.v1.Tapscripts + 40, // 21: ark.v1.Tree.levels:type_name -> ark.v1.TreeLevel + 41, // 22: ark.v1.TreeLevel.nodes:type_name -> ark.v1.Node + 36, // 23: ark.v1.Vtxo.outpoint:type_name -> ark.v1.Outpoint + 45, // 24: ark.v1.GetTransactionsStreamResponse.round:type_name -> ark.v1.RoundTransaction + 46, // 25: ark.v1.GetTransactionsStreamResponse.redeem:type_name -> ark.v1.RedeemTransaction + 36, // 26: ark.v1.RoundTransaction.spent_vtxos:type_name -> ark.v1.Outpoint + 42, // 27: ark.v1.RoundTransaction.spendable_vtxos:type_name -> ark.v1.Vtxo + 36, // 28: ark.v1.RoundTransaction.claimed_boarding_utxos:type_name -> ark.v1.Outpoint + 36, // 29: ark.v1.RedeemTransaction.spent_vtxos:type_name -> ark.v1.Outpoint + 42, // 30: ark.v1.RedeemTransaction.spendable_vtxos:type_name -> ark.v1.Vtxo + 36, // 31: ark.v1.SignedVtxoOutpoint.outpoint:type_name -> ark.v1.Outpoint + 47, // 32: ark.v1.SignedVtxoOutpoint.proof:type_name -> ark.v1.OwnershipProof + 48, // 33: ark.v1.SetNostrRecipientRequest.vtxos:type_name -> ark.v1.SignedVtxoOutpoint + 48, // 34: ark.v1.DeleteNostrRecipientRequest.vtxos:type_name -> ark.v1.SignedVtxoOutpoint + 1, // 35: ark.v1.ArkService.GetInfo:input_type -> ark.v1.GetInfoRequest + 3, // 36: ark.v1.ArkService.GetBoardingAddress:input_type -> ark.v1.GetBoardingAddressRequest + 5, // 37: ark.v1.ArkService.RegisterInputsForNextRound:input_type -> ark.v1.RegisterInputsForNextRoundRequest + 7, // 38: ark.v1.ArkService.RegisterOutputsForNextRound:input_type -> ark.v1.RegisterOutputsForNextRoundRequest + 9, // 39: ark.v1.ArkService.SubmitTreeNonces:input_type -> ark.v1.SubmitTreeNoncesRequest + 11, // 40: ark.v1.ArkService.SubmitTreeSignatures:input_type -> ark.v1.SubmitTreeSignaturesRequest + 13, // 41: ark.v1.ArkService.SubmitSignedForfeitTxs:input_type -> ark.v1.SubmitSignedForfeitTxsRequest + 15, // 42: ark.v1.ArkService.GetEventStream:input_type -> ark.v1.GetEventStreamRequest + 17, // 43: ark.v1.ArkService.Ping:input_type -> ark.v1.PingRequest + 20, // 44: ark.v1.ArkService.CreatePayment:input_type -> ark.v1.CreatePaymentRequest + 22, // 45: ark.v1.ArkService.CompletePayment:input_type -> ark.v1.CompletePaymentRequest + 24, // 46: ark.v1.ArkService.GetRound:input_type -> ark.v1.GetRoundRequest + 26, // 47: ark.v1.ArkService.GetRoundById:input_type -> ark.v1.GetRoundByIdRequest + 28, // 48: ark.v1.ArkService.ListVtxos:input_type -> ark.v1.ListVtxosRequest + 43, // 49: ark.v1.ArkService.GetTransactionsStream:input_type -> ark.v1.GetTransactionsStreamRequest + 49, // 50: ark.v1.ArkService.SetNostrRecipient:input_type -> ark.v1.SetNostrRecipientRequest + 51, // 51: ark.v1.ArkService.DeleteNostrRecipient:input_type -> ark.v1.DeleteNostrRecipientRequest + 2, // 52: ark.v1.ArkService.GetInfo:output_type -> ark.v1.GetInfoResponse + 4, // 53: ark.v1.ArkService.GetBoardingAddress:output_type -> ark.v1.GetBoardingAddressResponse + 6, // 54: ark.v1.ArkService.RegisterInputsForNextRound:output_type -> ark.v1.RegisterInputsForNextRoundResponse + 8, // 55: ark.v1.ArkService.RegisterOutputsForNextRound:output_type -> ark.v1.RegisterOutputsForNextRoundResponse + 10, // 56: ark.v1.ArkService.SubmitTreeNonces:output_type -> ark.v1.SubmitTreeNoncesResponse + 12, // 57: ark.v1.ArkService.SubmitTreeSignatures:output_type -> ark.v1.SubmitTreeSignaturesResponse + 14, // 58: ark.v1.ArkService.SubmitSignedForfeitTxs:output_type -> ark.v1.SubmitSignedForfeitTxsResponse + 16, // 59: ark.v1.ArkService.GetEventStream:output_type -> ark.v1.GetEventStreamResponse + 18, // 60: ark.v1.ArkService.Ping:output_type -> ark.v1.PingResponse + 21, // 61: ark.v1.ArkService.CreatePayment:output_type -> ark.v1.CreatePaymentResponse + 23, // 62: ark.v1.ArkService.CompletePayment:output_type -> ark.v1.CompletePaymentResponse + 25, // 63: ark.v1.ArkService.GetRound:output_type -> ark.v1.GetRoundResponse + 27, // 64: ark.v1.ArkService.GetRoundById:output_type -> ark.v1.GetRoundByIdResponse + 29, // 65: ark.v1.ArkService.ListVtxos:output_type -> ark.v1.ListVtxosResponse + 44, // 66: ark.v1.ArkService.GetTransactionsStream:output_type -> ark.v1.GetTransactionsStreamResponse + 50, // 67: ark.v1.ArkService.SetNostrRecipient:output_type -> ark.v1.SetNostrRecipientResponse + 52, // 68: ark.v1.ArkService.DeleteNostrRecipient:output_type -> ark.v1.DeleteNostrRecipientResponse + 52, // [52:69] is the sub-list for method output_type + 35, // [35:52] is the sub-list for method input_type + 35, // [35:35] is the sub-list for extension type_name + 35, // [35:35] is the sub-list for extension extendee + 0, // [0:35] is the sub-list for field type_name } func init() { file_ark_v1_service_proto_init() } @@ -4278,6 +4408,22 @@ func file_ark_v1_service_proto_init() { return nil } } + file_ark_v1_service_proto_msgTypes[52].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Tapscripts); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_ark_v1_service_proto_msgTypes[3].OneofWrappers = []interface{}{ + (*GetBoardingAddressResponse_Descriptor_)(nil), + (*GetBoardingAddressResponse_Tapscripts)(nil), } file_ark_v1_service_proto_msgTypes[4].OneofWrappers = []interface{}{} file_ark_v1_service_proto_msgTypes[12].OneofWrappers = []interface{}{} @@ -4288,6 +4434,10 @@ func file_ark_v1_service_proto_init() { (*GetEventStreamResponse_RoundSigning)(nil), (*GetEventStreamResponse_RoundSigningNoncesGenerated)(nil), } + file_ark_v1_service_proto_msgTypes[36].OneofWrappers = []interface{}{ + (*Input_Descriptor_)(nil), + (*Input_Tapscripts)(nil), + } file_ark_v1_service_proto_msgTypes[43].OneofWrappers = []interface{}{ (*GetTransactionsStreamResponse_Round)(nil), (*GetTransactionsStreamResponse_Redeem)(nil), @@ -4298,7 +4448,7 @@ func file_ark_v1_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_ark_v1_service_proto_rawDesc, NumEnums: 1, - NumMessages: 52, + NumMessages: 53, NumExtensions: 0, NumServices: 1, }, diff --git a/common/bitcointree/builder.go b/common/bitcointree/builder.go index e5a40e9..b27d93a 100644 --- a/common/bitcointree/builder.go +++ b/common/bitcointree/builder.go @@ -282,17 +282,19 @@ func createRootNode( func createAggregatedKeyWithSweep( cosigners []*secp256k1.PublicKey, aspPubkey *secp256k1.PublicKey, roundLifetime int64, ) (*musig2.AggregateKey, *psbt.TaprootTapLeafScript, error) { - sweepClosure := &CSVSigClosure{ - Pubkey: aspPubkey, - Seconds: uint(roundLifetime), + sweepClosure := &tree.CSVSigClosure{ + MultisigClosure: tree.MultisigClosure{PubKeys: []*secp256k1.PublicKey{aspPubkey}}, + Seconds: uint(roundLifetime), } - sweepLeaf, err := sweepClosure.Leaf() + sweepScript, err := sweepClosure.Script() if err != nil { return nil, nil, err } - tapTree := txscript.AssembleTaprootScriptTree(*sweepLeaf) + sweepLeaf := txscript.NewBaseTapLeaf(sweepScript) + + tapTree := txscript.AssembleTaprootScriptTree(sweepLeaf) tapTreeRoot := tapTree.RootNode.TapHash() aggregatedKey, err := AggregateKeys( diff --git a/common/bitcointree/musig2_test.go b/common/bitcointree/musig2_test.go index fe7a927..ad9c5ce 100644 --- a/common/bitcointree/musig2_test.go +++ b/common/bitcointree/musig2_test.go @@ -1,7 +1,6 @@ package bitcointree_test import ( - "encoding/hex" "encoding/json" "os" "testing" @@ -49,7 +48,7 @@ func TestRoundTripSignTree(t *testing.T) { ) require.NoError(t, err) - tree, err := bitcointree.CraftCongestionTree( + vtxoTree, err := bitcointree.CraftCongestionTree( &wire.OutPoint{ Hash: *testTxid, Index: 0, @@ -62,20 +61,21 @@ func TestRoundTripSignTree(t *testing.T) { ) require.NoError(t, err) - sweepClosure := bitcointree.CSVSigClosure{ - Pubkey: asp.PubKey(), - Seconds: lifetime, + sweepClosure := &tree.CSVSigClosure{ + MultisigClosure: tree.MultisigClosure{PubKeys: []*secp256k1.PublicKey{asp.PubKey()}}, + Seconds: uint(lifetime), } - sweepTapLeaf, err := sweepClosure.Leaf() + sweepScript, err := sweepClosure.Script() require.NoError(t, err) - sweepTapTree := txscript.AssembleTaprootScriptTree(*sweepTapLeaf) + sweepTapLeaf := txscript.NewBaseTapLeaf(sweepScript) + sweepTapTree := txscript.AssembleTaprootScriptTree(sweepTapLeaf) root := sweepTapTree.RootNode.TapHash() aspCoordinator, err := bitcointree.NewTreeCoordinatorSession( sharedOutputAmount, - tree, + vtxoTree, root.CloneBytes(), cosignerPubKeys, ) @@ -84,7 +84,7 @@ func TestRoundTripSignTree(t *testing.T) { // Create signer sessions for all cosigners signerSessions := make([]bitcointree.SignerSession, 20) for i, cosigner := range cosigners { - signerSessions[i] = bitcointree.NewTreeSignerSession(cosigner, sharedOutputAmount, tree, root.CloneBytes()) + signerSessions[i] = bitcointree.NewTreeSignerSession(cosigner, sharedOutputAmount, vtxoTree, root.CloneBytes()) } // Get nonces from all signers @@ -136,24 +136,6 @@ type receiverFixture struct { Pubkey string `json:"pubkey"` } -func (r receiverFixture) toVtxoScript(asp *secp256k1.PublicKey) bitcointree.VtxoScript { - bytesKey, err := hex.DecodeString(r.Pubkey) - if err != nil { - panic(err) - } - - pubkey, err := secp256k1.ParsePubKey(bytesKey) - if err != nil { - panic(err) - } - - return &bitcointree.DefaultVtxoScript{ - Owner: pubkey, - Asp: asp, - ExitDelay: exitDelay, - } -} - func castReceivers(receivers []receiverFixture) []tree.VtxoLeaf { receiversOut := make([]tree.VtxoLeaf, 0, len(receivers)) for _, r := range receivers { diff --git a/common/bitcointree/script.go b/common/bitcointree/script.go deleted file mode 100644 index 5e799fe..0000000 --- a/common/bitcointree/script.go +++ /dev/null @@ -1,203 +0,0 @@ -package bitcointree - -import ( - "bytes" - "fmt" - - "github.com/ark-network/ark/common" - "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/txscript" - "github.com/decred/dcrd/dcrec/secp256k1/v4" -) - -type Closure interface { - Leaf() (*txscript.TapLeaf, error) - Decode(script []byte) (bool, error) -} - -type CSVSigClosure struct { - Pubkey *secp256k1.PublicKey - Seconds uint -} - -type MultisigClosure struct { - Pubkey *secp256k1.PublicKey - AspPubkey *secp256k1.PublicKey -} - -func DecodeClosure(script []byte) (Closure, error) { - var closure Closure - - closure = &CSVSigClosure{} - if valid, err := closure.Decode(script); err == nil && valid { - return closure, nil - } - - closure = &MultisigClosure{} - if valid, err := closure.Decode(script); err == nil && valid { - return closure, nil - } - - return nil, fmt.Errorf("invalid closure script") - -} - -func (f *MultisigClosure) Leaf() (*txscript.TapLeaf, error) { - aspKeyBytes := schnorr.SerializePubKey(f.AspPubkey) - userKeyBytes := schnorr.SerializePubKey(f.Pubkey) - - script, err := txscript.NewScriptBuilder().AddData(aspKeyBytes). - AddOp(txscript.OP_CHECKSIGVERIFY).AddData(userKeyBytes). - AddOp(txscript.OP_CHECKSIG).Script() - if err != nil { - return nil, err - } - - tapLeaf := txscript.NewBaseTapLeaf(script) - return &tapLeaf, nil -} - -func (f *MultisigClosure) Decode(script []byte) (bool, error) { - valid, aspPubKey, err := decodeChecksigScript(script) - if err != nil { - return false, err - } - - if !valid { - return false, nil - } - - valid, pubkey, err := decodeChecksigScript(script[33:]) - if err != nil { - return false, err - } - - if !valid { - return false, nil - } - - f.Pubkey = pubkey - f.AspPubkey = aspPubKey - - rebuilt, err := f.Leaf() - if err != nil { - return false, err - } - - if !bytes.Equal(rebuilt.Script, script) { - return false, nil - } - - return true, nil -} - -func (d *CSVSigClosure) Leaf() (*txscript.TapLeaf, error) { - script, err := encodeCsvWithChecksigScript(d.Pubkey, d.Seconds) - if err != nil { - return nil, err - } - - tapLeaf := txscript.NewBaseTapLeaf(script) - return &tapLeaf, nil -} - -func (d *CSVSigClosure) Decode(script []byte) (bool, error) { - csvIndex := bytes.Index( - script, []byte{txscript.OP_CHECKSEQUENCEVERIFY, txscript.OP_DROP}, - ) - if csvIndex == -1 || csvIndex == 0 { - return false, nil - } - - sequence := script[:csvIndex] - if len(sequence) > 1 { - sequence = sequence[1:] - } - - seconds, err := common.BIP68DecodeSequence(sequence) - if err != nil { - return false, err - } - - checksigScript := script[csvIndex+2:] - valid, pubkey, err := decodeChecksigScript(checksigScript) - if err != nil { - return false, err - } - - if !valid { - return false, nil - } - - rebuilt, err := encodeCsvWithChecksigScript(pubkey, seconds) - if err != nil { - return false, err - } - - if !bytes.Equal(rebuilt, script) { - return false, nil - } - - d.Pubkey = pubkey - d.Seconds = seconds - - return valid, nil -} - -func decodeChecksigScript(script []byte) (bool, *secp256k1.PublicKey, error) { - data32Index := bytes.Index(script, []byte{txscript.OP_DATA_32}) - if data32Index == -1 { - return false, nil, nil - } - - key := script[data32Index+1 : data32Index+33] - if len(key) != 32 { - return false, nil, nil - } - - pubkey, err := schnorr.ParsePubKey(key) - if err != nil { - return false, nil, err - } - - return true, pubkey, nil -} - -// checkSequenceVerifyScript without checksig -func encodeCsvScript(seconds uint) ([]byte, error) { - sequence, err := common.BIP68Sequence(seconds) - if err != nil { - return nil, err - } - - return txscript.NewScriptBuilder(). - AddInt64(int64(sequence)). - AddOps([]byte{ - txscript.OP_CHECKSEQUENCEVERIFY, - txscript.OP_DROP, - }). - Script() -} - -// checkSequenceVerifyScript + checksig -func encodeCsvWithChecksigScript( - pubkey *secp256k1.PublicKey, seconds uint, -) ([]byte, error) { - script, err := encodeChecksigScript(pubkey) - if err != nil { - return nil, err - } - - csvScript, err := encodeCsvScript(seconds) - if err != nil { - return nil, err - } - - return append(csvScript, script...), nil -} - -func encodeChecksigScript(pubkey *secp256k1.PublicKey) ([]byte, error) { - key := schnorr.SerializePubKey(pubkey) - return txscript.NewScriptBuilder().AddData(key). - AddOp(txscript.OP_CHECKSIG).Script() -} diff --git a/common/bitcointree/script_test.go b/common/bitcointree/script_test.go deleted file mode 100644 index d71c8d5..0000000 --- a/common/bitcointree/script_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package bitcointree_test - -import ( - "testing" - - "github.com/ark-network/ark/common/bitcointree" - "github.com/decred/dcrd/dcrec/secp256k1/v4" - "github.com/stretchr/testify/require" -) - -func TestRoundTripCSV(t *testing.T) { - seckey, err := secp256k1.GeneratePrivateKey() - require.NoError(t, err) - - csvSig := &bitcointree.CSVSigClosure{ - Pubkey: seckey.PubKey(), - Seconds: 1024, - } - - leaf, err := csvSig.Leaf() - require.NoError(t, err) - - var cl bitcointree.CSVSigClosure - - valid, err := cl.Decode(leaf.Script) - require.NoError(t, err) - require.True(t, valid) - - require.Equal(t, csvSig.Seconds, cl.Seconds) -} diff --git a/common/bitcointree/validation.go b/common/bitcointree/validation.go index 9e8fab3..bb9dc6e 100644 --- a/common/bitcointree/validation.go +++ b/common/bitcointree/validation.go @@ -72,7 +72,7 @@ func UnspendableKey() *secp256k1.PublicKey { // - every control block and taproot output scripts // - input and output amounts func ValidateCongestionTree( - tree tree.CongestionTree, poolTx string, aspPublicKey *secp256k1.PublicKey, roundLifetime int64, + vtxoTree tree.CongestionTree, poolTx string, aspPublicKey *secp256k1.PublicKey, roundLifetime int64, ) error { poolTransaction, err := psbt.NewFromRawBytes(strings.NewReader(poolTx), true) if err != nil { @@ -85,17 +85,17 @@ func ValidateCongestionTree( poolTxAmount := poolTransaction.UnsignedTx.TxOut[sharedOutputIndex].Value - nbNodes := tree.NumberOfNodes() + nbNodes := vtxoTree.NumberOfNodes() if nbNodes == 0 { return ErrEmptyTree } - if len(tree[0]) != 1 { + if len(vtxoTree[0]) != 1 { return ErrInvalidRootLevel } // check that root input is connected to the pool tx - rootPsetB64 := tree[0][0].Tx + rootPsetB64 := vtxoTree[0][0].Tx rootPset, err := psbt.NewFromRawBytes(strings.NewReader(rootPsetB64), true) if err != nil { return fmt.Errorf("invalid root transaction: %w", err) @@ -120,28 +120,29 @@ func ValidateCongestionTree( return ErrInvalidAmount } - if len(tree.Leaves()) == 0 { + if len(vtxoTree.Leaves()) == 0 { return ErrNoLeaves } - sweepClosure := &CSVSigClosure{ - Seconds: uint(roundLifetime), - Pubkey: aspPublicKey, + sweepClosure := &tree.CSVSigClosure{ + MultisigClosure: tree.MultisigClosure{PubKeys: []*secp256k1.PublicKey{aspPublicKey}}, + Seconds: uint(roundLifetime), } - sweepLeaf, err := sweepClosure.Leaf() + sweepScript, err := sweepClosure.Script() if err != nil { return err } - tapTree := txscript.AssembleTaprootScriptTree(*sweepLeaf) + sweepLeaf := txscript.NewBaseTapLeaf(sweepScript) + tapTree := txscript.AssembleTaprootScriptTree(sweepLeaf) root := tapTree.RootNode.TapHash() // iterates over all the nodes of the tree - for _, level := range tree { + for _, level := range vtxoTree { for _, node := range level { if err := validateNodeTransaction( - node, tree, root.CloneBytes(), + node, vtxoTree, root.CloneBytes(), ); err != nil { return err } diff --git a/common/bitcointree/vtxo.go b/common/bitcointree/vtxo.go index a8bf770..97d86d5 100644 --- a/common/bitcointree/vtxo.go +++ b/common/bitcointree/vtxo.go @@ -1,94 +1,52 @@ package bitcointree import ( - "encoding/hex" "fmt" "github.com/ark-network/ark/common" - "github.com/ark-network/ark/common/descriptor" - "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/ark-network/ark/common/tree" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/decred/dcrd/dcrec/secp256k1/v4" ) -type VtxoScript common.VtxoScript[bitcoinTapTree] +type VtxoScript common.VtxoScript[bitcoinTapTree, *tree.MultisigClosure, *tree.CSVSigClosure] -func ParseVtxoScript(desc string) (VtxoScript, error) { +func ParseVtxoScript(scripts []string) (VtxoScript, error) { types := []VtxoScript{ - &DefaultVtxoScript{}, + &TapscriptsVtxoScript{}, } for _, v := range types { - if err := v.FromDescriptor(desc); err == nil { + if err := v.Decode(scripts); err == nil { return v, nil } } - return nil, fmt.Errorf("invalid vtxo descriptor: %s", desc) + return nil, fmt.Errorf("invalid vtxo scripts: %s", scripts) } -/* -* DefaultVtxoScript is the default implementation of VTXO with 2 closures -* - Owner and ASP (forfeit) -* - Owner after t (unilateral exit) - */ -type DefaultVtxoScript struct { - Owner *secp256k1.PublicKey - Asp *secp256k1.PublicKey - ExitDelay uint +func NewDefaultVtxoScript(owner, asp *secp256k1.PublicKey, exitDelay uint) VtxoScript { + base := tree.NewDefaultVtxoScript(owner, asp, exitDelay) + + return &TapscriptsVtxoScript{*base} } -func (v *DefaultVtxoScript) ToDescriptor() string { - owner := hex.EncodeToString(schnorr.SerializePubKey(v.Owner)) - - return fmt.Sprintf( - descriptor.DefaultVtxoDescriptorTemplate, - hex.EncodeToString(UnspendableKey().SerializeCompressed()), - owner, - hex.EncodeToString(schnorr.SerializePubKey(v.Asp)), - v.ExitDelay, - owner, - ) +type TapscriptsVtxoScript struct { + tree.TapscriptsVtxoScript } -func (v *DefaultVtxoScript) FromDescriptor(desc string) error { - owner, asp, exitDelay, err := descriptor.ParseDefaultVtxoDescriptor(desc) - if err != nil { - return err +func (v *TapscriptsVtxoScript) TapTree() (*secp256k1.PublicKey, bitcoinTapTree, error) { + leaves := make([]txscript.TapLeaf, len(v.Closures)) + for i, closure := range v.Closures { + script, err := closure.Script() + if err != nil { + return nil, bitcoinTapTree{}, fmt.Errorf("failed to get script for closure %d: %w", i, err) + } + leaves[i] = txscript.NewBaseTapLeaf(script) } - v.Owner = owner - v.Asp = asp - v.ExitDelay = exitDelay - return nil -} - -func (v *DefaultVtxoScript) TapTree() (*secp256k1.PublicKey, bitcoinTapTree, error) { - redeemClosure := &CSVSigClosure{ - Pubkey: v.Owner, - Seconds: v.ExitDelay, - } - - redeemLeaf, err := redeemClosure.Leaf() - if err != nil { - return nil, bitcoinTapTree{}, err - } - - forfeitClosure := &MultisigClosure{ - Pubkey: v.Owner, - AspPubkey: v.Asp, - } - - forfeitLeaf, err := forfeitClosure.Leaf() - if err != nil { - return nil, bitcoinTapTree{}, err - } - - tapTree := txscript.AssembleTaprootScriptTree( - *redeemLeaf, *forfeitLeaf, - ) - + tapTree := txscript.AssembleTaprootScriptTree(leaves...) root := tapTree.RootNode.TapHash() taprootKey := txscript.ComputeTaprootOutputKey( UnspendableKey(), diff --git a/common/bitcointree/vtxo_test.go b/common/bitcointree/vtxo_test.go deleted file mode 100644 index 271b903..0000000 --- a/common/bitcointree/vtxo_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package bitcointree_test - -import ( - "encoding/hex" - "fmt" - "testing" - - "github.com/ark-network/ark/common/bitcointree" - "github.com/ark-network/ark/common/descriptor" - "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/decred/dcrd/dcrec/secp256k1/v4" - "github.com/stretchr/testify/require" -) - -func TestParseDescriptor(t *testing.T) { - aspKey, err := secp256k1.GeneratePrivateKey() - require.NoError(t, err) - - aliceKey, err := secp256k1.GeneratePrivateKey() - require.NoError(t, err) - - aspPubKey := hex.EncodeToString(schnorr.SerializePubKey(aspKey.PubKey())) - alicePubKey := hex.EncodeToString(schnorr.SerializePubKey(aliceKey.PubKey())) - - unspendableKey := hex.EncodeToString(bitcointree.UnspendableKey().SerializeCompressed()) - - defaultScriptDescriptor := fmt.Sprintf( - descriptor.DefaultVtxoDescriptorTemplate, - unspendableKey, - alicePubKey, - aspPubKey, - 512, - alicePubKey, - ) - - vtxo, err := bitcointree.ParseVtxoScript(defaultScriptDescriptor) - require.NoError(t, err) - - require.IsType(t, &bitcointree.DefaultVtxoScript{}, vtxo) - require.Equal(t, defaultScriptDescriptor, vtxo.ToDescriptor()) - require.Equal(t, alicePubKey, hex.EncodeToString(schnorr.SerializePubKey(vtxo.(*bitcointree.DefaultVtxoScript).Owner))) - require.Equal(t, aspPubKey, hex.EncodeToString(schnorr.SerializePubKey(vtxo.(*bitcointree.DefaultVtxoScript).Asp))) -} diff --git a/common/tree/builder.go b/common/tree/builder.go index 61741f3..4668bf2 100644 --- a/common/tree/builder.go +++ b/common/tree/builder.go @@ -163,11 +163,11 @@ func (n *node) getWitnessData() ( } sweepClosure := &CSVSigClosure{ - Pubkey: n.sweepKey, - Seconds: uint(n.roundLifetime), + MultisigClosure: MultisigClosure{PubKeys: []*secp256k1.PublicKey{n.sweepKey}}, + Seconds: uint(n.roundLifetime), } - sweepLeaf, err := sweepClosure.Leaf() + sweepLeaf, err := sweepClosure.Script() if err != nil { return nil, nil, err } @@ -183,13 +183,14 @@ func (n *node) getWitnessData() ( MinRelayFee: n.feeSats, } - unrollLeaf, err := unrollClosure.Leaf() + unrollScript, err := unrollClosure.Script() if err != nil { return nil, nil, err } branchTaprootTree := taproot.AssembleTaprootScriptTree( - *unrollLeaf, *sweepLeaf, + taproot.NewBaseTapElementsLeaf(unrollScript), + taproot.NewBaseTapElementsLeaf(sweepLeaf), ) root := branchTaprootTree.RootNode.TapHash() @@ -224,13 +225,14 @@ func (n *node) getWitnessData() ( RightAmount: rightAmount, } - unrollLeaf, err := unrollClosure.Leaf() + unrollLeaf, err := unrollClosure.Script() if err != nil { return nil, nil, err } branchTaprootTree := taproot.AssembleTaprootScriptTree( - *unrollLeaf, *sweepLeaf, + taproot.NewBaseTapElementsLeaf(unrollLeaf), + taproot.NewBaseTapElementsLeaf(sweepLeaf), ) root := branchTaprootTree.RootNode.TapHash() diff --git a/common/tree/script.go b/common/tree/script.go index 1a7f56f..726a69c 100644 --- a/common/tree/script.go +++ b/common/tree/script.go @@ -9,8 +9,8 @@ import ( "github.com/ark-network/ark/common" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/decred/dcrd/dcrec/secp256k1/v4" - "github.com/vulpemventures/go-elements/taproot" ) const ( @@ -21,108 +21,333 @@ const ( OP_SUB64 = 0xd8 ) +type MultisigType int + +const ( + MultisigTypeChecksig MultisigType = iota + MultisigTypeChecksigAdd +) + type Closure interface { - Leaf() (*taproot.TapElementsLeaf, error) + Script() ([]byte, error) Decode(script []byte) (bool, error) + // WitnessSize returns the size of the witness excluding the script and control block + WitnessSize() int + Witness(controlBlock []byte, signatures map[string][]byte) (wire.TxWitness, error) } +// UnrollClosure is liquid-only tapscript letting to enforce +// unrollable UTXO without musig. type UnrollClosure struct { LeftKey, RightKey *secp256k1.PublicKey LeftAmount, RightAmount uint64 MinRelayFee uint64 } +// MultisigClosure is a closure that contains a list of public keys and a +// CHECKSIG for each key. The witness size is 64 bytes per key, admitting the +// sighash type is SIGHASH_DEFAULT. +type MultisigClosure struct { + PubKeys []*secp256k1.PublicKey + Type MultisigType +} + +// CSVSigClosure is a closure that contains a list of public keys and a +// CHECKSEQUENCEVERIFY + DROP. The witness size is 64 bytes per key, admitting +// the sighash type is SIGHASH_DEFAULT. type CSVSigClosure struct { - Pubkey *secp256k1.PublicKey + MultisigClosure Seconds uint } -type MultisigClosure struct { - Pubkey *secp256k1.PublicKey - AspPubkey *secp256k1.PublicKey -} - func DecodeClosure(script []byte) (Closure, error) { - var closure Closure - - closure = &UnrollClosure{} - if valid, err := closure.Decode(script); err == nil && valid { - return closure, nil + types := []Closure{ + &CSVSigClosure{}, + &MultisigClosure{}, + &UnrollClosure{}, } - closure = &CSVSigClosure{} - if valid, err := closure.Decode(script); err == nil && valid { - return closure, nil - } - - closure = &MultisigClosure{} - if valid, err := closure.Decode(script); err == nil && valid { - return closure, nil + for _, closure := range types { + if valid, err := closure.Decode(script); err == nil && valid { + return closure, nil + } } return nil, fmt.Errorf("invalid closure script %s", hex.EncodeToString(script)) } -func (f *MultisigClosure) Leaf() (*taproot.TapElementsLeaf, error) { - aspKeyBytes := schnorr.SerializePubKey(f.AspPubkey) - userKeyBytes := schnorr.SerializePubKey(f.Pubkey) +func (f *MultisigClosure) WitnessSize() int { + return 64 * len(f.PubKeys) +} - script, err := txscript.NewScriptBuilder().AddData(aspKeyBytes). - AddOp(txscript.OP_CHECKSIGVERIFY).AddData(userKeyBytes). - AddOp(txscript.OP_CHECKSIG).Script() - if err != nil { - return nil, err +func (f *MultisigClosure) Script() ([]byte, error) { + scriptBuilder := txscript.NewScriptBuilder() + + switch f.Type { + case MultisigTypeChecksig: + for i, pubkey := range f.PubKeys { + scriptBuilder.AddData(schnorr.SerializePubKey(pubkey)) + if i == len(f.PubKeys)-1 { + scriptBuilder.AddOp(txscript.OP_CHECKSIG) + continue + } + scriptBuilder.AddOp(txscript.OP_CHECKSIGVERIFY) + } + case MultisigTypeChecksigAdd: + for i, pubkey := range f.PubKeys { + scriptBuilder.AddData(schnorr.SerializePubKey(pubkey)) + if i == 0 { + scriptBuilder.AddOp(txscript.OP_CHECKSIG) + continue + } + scriptBuilder.AddOp(txscript.OP_CHECKSIGADD) + } + scriptBuilder.AddInt64(int64(len(f.PubKeys))) + scriptBuilder.AddOp(txscript.OP_EQUAL) } - tapLeaf := taproot.NewBaseTapElementsLeaf(script) - return &tapLeaf, nil + return scriptBuilder.Script() } func (f *MultisigClosure) Decode(script []byte) (bool, error) { - valid, aspPubKey, err := decodeChecksigScript(script) - if err != nil { - return false, err + if len(script) == 0 { + return false, fmt.Errorf("empty script") } - if !valid { + valid, err := f.decodeChecksig(script) + if err != nil { + return false, fmt.Errorf("failed to decode checksig: %w", err) + } + if valid { + return true, nil + } + + valid, err = f.decodeChecksigAdd(script) + if err != nil { + return false, fmt.Errorf("failed to decode checksigadd: %w", err) + } + if valid { + return valid, nil + } + + return false, nil +} + +func (f *MultisigClosure) decodeChecksigAdd(script []byte) (bool, error) { + pubkeys := make([]*secp256k1.PublicKey, 0) + + // Keep track of position in script + pos := 0 + + for pos < len(script) { + // Check for 33-byte data push (32 bytes for pubkey + 1 byte for OP_DATA) + if pos+33 > len(script) { + break + } + + // Verify we have a 32-byte data push + if script[pos] != txscript.OP_DATA_32 { + return false, nil + } + + // Parse the public key + pubkey, err := schnorr.ParsePubKey(script[pos+1 : pos+33]) + if err != nil { + return false, err + } + + pubkeys = append(pubkeys, pubkey) + pos += 33 + + // Check if we've reached the end + if pos >= len(script) { + return false, nil + } + + // Check for CHECKSIG or CHECKSIGADD pattern + if len(pubkeys) == 1 && script[pos] == txscript.OP_CHECKSIG { + pos++ + } else if len(pubkeys) > 1 && script[pos] == txscript.OP_CHECKSIGADD { + pos++ + } else { + return false, nil + } + } + + lastOp := script[len(script)-1] + if lastOp != txscript.OP_EQUAL { return false, nil } - valid, pubkey, err := decodeChecksigScript(script[33:]) - if err != nil { - return false, err - } - - if !valid { + // Verify we found at least one public key + if len(pubkeys) == 0 { return false, nil } - f.Pubkey = pubkey - f.AspPubkey = aspPubKey + f.PubKeys = pubkeys + f.Type = MultisigTypeChecksigAdd - rebuilt, err := f.Leaf() + // Verify the script matches what we would generate + rebuilt, err := f.Script() if err != nil { + f.PubKeys = nil + f.Type = 0 return false, err } - if !bytes.Equal(rebuilt.Script, script) { + if !bytes.Equal(rebuilt, script) { + f.PubKeys = nil + f.Type = 0 return false, nil } return true, nil } -func (d *CSVSigClosure) Leaf() (*taproot.TapElementsLeaf, error) { - script, err := encodeCsvWithChecksigScript(d.Pubkey, d.Seconds) +func (f *MultisigClosure) decodeChecksig(script []byte) (bool, error) { + pubkeys := make([]*secp256k1.PublicKey, 0) + + // Keep track of position in script + pos := 0 + + for pos < len(script) { + // Check for 33-byte data push (32 bytes for pubkey + 1 byte for OP_DATA) + if pos+33 > len(script) { + return false, nil + } + + // Verify we have a 32-byte data push + if script[pos] != txscript.OP_DATA_32 { + return false, nil + } + + // Parse the public key + pubkey, err := schnorr.ParsePubKey(script[pos+1 : pos+33]) + if err != nil { + return false, err + } + + pubkeys = append(pubkeys, pubkey) + pos += 33 + + // Check if we've reached the end + if pos >= len(script) { + return false, nil + } + + // Next byte should be either CHECKSIG (last key) or CHECKSIGVERIFY + if script[pos] == txscript.OP_CHECKSIG { + // This should be the last operation + if pos != len(script)-1 { + return false, nil + } + break + } else if script[pos] == txscript.OP_CHECKSIGVERIFY { + pos++ + continue + } else { + return false, nil + } + } + + // Verify we found at least one public key + if len(pubkeys) == 0 { + return false, nil + } + + f.PubKeys = pubkeys + f.Type = MultisigTypeChecksig + + // Verify the script matches what we would generate + rebuilt, err := f.Script() + if err != nil { + f.PubKeys = nil + f.Type = 0 + return false, err + } + + if !bytes.Equal(rebuilt, script) { + f.PubKeys = nil + f.Type = 0 + return false, nil + } + + return true, nil +} + +func (f *MultisigClosure) Witness(controlBlock []byte, signatures map[string][]byte) (wire.TxWitness, error) { + // Create witness stack with capacity for all signatures plus script and control block + witness := make(wire.TxWitness, 0, len(f.PubKeys)+2) + + // Add signatures in the reverse order as public keys + for i := len(f.PubKeys) - 1; i >= 0; i-- { + pubKey := f.PubKeys[i] + sig, ok := signatures[hex.EncodeToString(schnorr.SerializePubKey(pubKey))] + if !ok { + return nil, fmt.Errorf("missing signature for public key %x", schnorr.SerializePubKey(pubKey)) + } + witness = append(witness, sig) + } + + // Get script + script, err := f.Script() + if err != nil { + return nil, fmt.Errorf("failed to generate script: %w", err) + } + + // Add script and control block + witness = append(witness, script) + witness = append(witness, controlBlock) + + return witness, nil +} + +func (f *CSVSigClosure) Witness(controlBlock []byte, signatures map[string][]byte) (wire.TxWitness, error) { + multisigWitness, err := f.MultisigClosure.Witness(controlBlock, signatures) if err != nil { return nil, err } - tapLeaf := taproot.NewBaseTapElementsLeaf(script) - return &tapLeaf, nil + script, err := f.Script() + if err != nil { + return nil, fmt.Errorf("failed to generate script: %w", err) + } + + // replace script with csv script + multisigWitness[len(multisigWitness)-2] = script + + return multisigWitness, nil +} + +func (f *CSVSigClosure) WitnessSize() int { + return f.MultisigClosure.WitnessSize() +} + +func (d *CSVSigClosure) Script() ([]byte, error) { + csvScript, err := txscript.NewScriptBuilder(). + AddInt64(int64(d.Seconds)). + AddOps([]byte{ + txscript.OP_CHECKSEQUENCEVERIFY, + txscript.OP_DROP, + }). + Script() + if err != nil { + return nil, err + } + + multisigScript, err := d.MultisigClosure.Script() + if err != nil { + return nil, err + } + + return append(csvScript, multisigScript...), nil } func (d *CSVSigClosure) Decode(script []byte) (bool, error) { + if len(script) == 0 { + return false, fmt.Errorf("empty script") + } + csvIndex := bytes.Index( script, []byte{txscript.OP_CHECKSEQUENCEVERIFY, txscript.OP_DROP}, ) @@ -140,8 +365,8 @@ func (d *CSVSigClosure) Decode(script []byte) (bool, error) { return false, err } - checksigScript := script[csvIndex+2:] - valid, pubkey, err := decodeChecksigScript(checksigScript) + multisigClosure := &MultisigClosure{} + valid, err := multisigClosure.Decode(script[csvIndex+2:]) if err != nil { return false, err } @@ -150,26 +375,21 @@ func (d *CSVSigClosure) Decode(script []byte) (bool, error) { return false, nil } - rebuilt, err := encodeCsvWithChecksigScript(pubkey, seconds) - if err != nil { - return false, err - } - - if !bytes.Equal(rebuilt, script) { - return false, nil - } - - d.Pubkey = pubkey d.Seconds = seconds + d.MultisigClosure = *multisigClosure return valid, nil } +func (c *UnrollClosure) WitnessSize() int { + return 0 +} + func (c *UnrollClosure) isOneChild() bool { return c.RightKey == nil && c.MinRelayFee > 0 } -func (c *UnrollClosure) Leaf() (*taproot.TapElementsLeaf, error) { +func (c *UnrollClosure) Script() ([]byte, error) { if c.LeftKey == nil { return nil, fmt.Errorf("left key is required") } @@ -178,8 +398,7 @@ func (c *UnrollClosure) Leaf() (*taproot.TapElementsLeaf, error) { branchScript := encodeOneChildIntrospectionScript( txscript.OP_0, schnorr.SerializePubKey(c.LeftKey), c.MinRelayFee, ) - leaf := taproot.NewBaseTapElementsLeaf(branchScript) - return &leaf, nil + return branchScript, nil } if c.LeftAmount == 0 { @@ -205,8 +424,7 @@ func (c *UnrollClosure) Leaf() (*taproot.TapElementsLeaf, error) { ) branchScript = append(branchScript, nextScriptRight...) - leaf := taproot.NewBaseTapElementsLeaf(branchScript) - return &leaf, nil + return branchScript, nil } func (c *UnrollClosure) Decode(script []byte) (valid bool, err error) { @@ -227,12 +445,12 @@ func (c *UnrollClosure) Decode(script []byte) (valid bool, err error) { c.LeftKey = pubkey c.MinRelayFee = minrelayfee - rebuilt, err := c.Leaf() + rebuilt, err := c.Script() if err != nil { return false, err } - if !bytes.Equal(rebuilt.Script, script) { + if !bytes.Equal(rebuilt, script) { return false, nil } @@ -272,12 +490,12 @@ func (c *UnrollClosure) Decode(script []byte) (valid bool, err error) { c.RightAmount = rightAmount c.RightKey = rightKey - rebuilt, err := c.Leaf() + rebuilt, err := c.Script() if err != nil { return false, err } - if !bytes.Equal(rebuilt.Script, script) { + if !bytes.Equal(rebuilt, script) { return false, nil } @@ -351,64 +569,6 @@ func decodeOneChildIntrospectionScript( return true, pubkey, minrelayfee, nil } -func decodeChecksigScript(script []byte) (bool, *secp256k1.PublicKey, error) { - data32Index := bytes.Index(script, []byte{txscript.OP_DATA_32}) - if data32Index == -1 { - return false, nil, nil - } - - key := script[data32Index+1 : data32Index+33] - if len(key) != 32 { - return false, nil, nil - } - - pubkey, err := schnorr.ParsePubKey(key) - if err != nil { - return false, nil, err - } - - return true, pubkey, nil -} - -// checkSequenceVerifyScript without checksig -func encodeCsvScript(seconds uint) ([]byte, error) { - sequence, err := common.BIP68Sequence(seconds) - if err != nil { - return nil, err - } - - return txscript.NewScriptBuilder(). - AddInt64(int64(sequence)). - AddOps([]byte{ - txscript.OP_CHECKSEQUENCEVERIFY, - txscript.OP_DROP, - }). - Script() -} - -// checkSequenceVerifyScript + checksig -func encodeCsvWithChecksigScript( - pubkey *secp256k1.PublicKey, seconds uint, -) ([]byte, error) { - script, err := encodeChecksigScript(pubkey) - if err != nil { - return nil, err - } - - csvScript, err := encodeCsvScript(seconds) - if err != nil { - return nil, err - } - - return append(csvScript, script...), nil -} - -func encodeChecksigScript(pubkey *secp256k1.PublicKey) ([]byte, error) { - key := schnorr.SerializePubKey(pubkey) - return txscript.NewScriptBuilder().AddData(key). - AddOp(txscript.OP_CHECKSIG).Script() -} - // encodeIntrospectionScript returns an introspection script that checks the // script and the amount of the output at the given index verify will add an // OP_EQUALVERIFY at the end of the script, otherwise it will add an OP_EQUAL @@ -496,3 +656,13 @@ func encodeOneChildIntrospectionScript( return script } + +func (c *UnrollClosure) Witness(controlBlock []byte, _ map[string][]byte) (wire.TxWitness, error) { + script, err := c.Script() + if err != nil { + return nil, fmt.Errorf("failed to generate script: %w", err) + } + + // UnrollClosure only needs script and control block + return wire.TxWitness{script, controlBlock}, nil +} diff --git a/common/tree/script_test.go b/common/tree/script_test.go new file mode 100644 index 0000000..18e6026 --- /dev/null +++ b/common/tree/script_test.go @@ -0,0 +1,471 @@ +package tree_test + +import ( + "encoding/hex" + "testing" + + "github.com/ark-network/ark/common/tree" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/txscript" + "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/stretchr/testify/require" +) + +func TestRoundTripCSV(t *testing.T) { + seckey, err := secp256k1.GeneratePrivateKey() + require.NoError(t, err) + + csvSig := &tree.CSVSigClosure{ + MultisigClosure: tree.MultisigClosure{ + PubKeys: []*secp256k1.PublicKey{seckey.PubKey()}, + }, + Seconds: 1024, + } + + leaf, err := csvSig.Script() + require.NoError(t, err) + + var cl tree.CSVSigClosure + + valid, err := cl.Decode(leaf) + require.NoError(t, err) + require.True(t, valid) + + require.Equal(t, csvSig.Seconds, cl.Seconds) +} + +func TestMultisigClosure(t *testing.T) { + // Generate some test keys + privKey1, err := secp256k1.GeneratePrivateKey() + require.NoError(t, err) + pubKey1 := privKey1.PubKey() + + privKey2, err := secp256k1.GeneratePrivateKey() + require.NoError(t, err) + pubKey2 := privKey2.PubKey() + + t.Run("valid 2-of-2 multisig", func(t *testing.T) { + closure := &tree.MultisigClosure{ + PubKeys: []*secp256k1.PublicKey{pubKey1, pubKey2}, + } + + // Generate script + script, err := closure.Script() + require.NoError(t, err) + + // Test decoding + decodedClosure := &tree.MultisigClosure{} + valid, err := decodedClosure.Decode(script) + require.NoError(t, err) + require.True(t, valid) + require.Equal(t, 2, len(decodedClosure.PubKeys)) + + // Compare serialized pubkeys + require.Equal(t, + schnorr.SerializePubKey(pubKey1), + schnorr.SerializePubKey(decodedClosure.PubKeys[0]), + ) + require.Equal(t, + schnorr.SerializePubKey(pubKey2), + schnorr.SerializePubKey(decodedClosure.PubKeys[1]), + ) + }) + + t.Run("valid single key multisig", func(t *testing.T) { + closure := &tree.MultisigClosure{ + PubKeys: []*secp256k1.PublicKey{pubKey1}, + } + + script, err := closure.Script() + require.NoError(t, err) + + decodedClosure := &tree.MultisigClosure{} + valid, err := decodedClosure.Decode(script) + require.NoError(t, err) + require.True(t, valid) + require.Equal(t, 1, len(decodedClosure.PubKeys)) + + // Compare serialized pubkey + require.Equal(t, + schnorr.SerializePubKey(pubKey1), + schnorr.SerializePubKey(decodedClosure.PubKeys[0]), + ) + }) + + t.Run("invalid empty script", func(t *testing.T) { + closure := &tree.MultisigClosure{} + valid, err := closure.Decode([]byte{}) + require.Error(t, err) + require.False(t, valid) + }) + + t.Run("invalid script - wrong data push", func(t *testing.T) { + script := []byte{ + txscript.OP_DATA_33, // Wrong size for schnorr pubkey + } + closure := &tree.MultisigClosure{} + valid, err := closure.Decode(script) + require.NoError(t, err) + require.False(t, valid) + }) + + t.Run("invalid script - missing checksig", func(t *testing.T) { + pubKeyBytes := schnorr.SerializePubKey(pubKey1) + script := []byte{txscript.OP_DATA_32} + script = append(script, pubKeyBytes...) + // Missing OP_CHECKSIG + + closure := &tree.MultisigClosure{} + valid, err := closure.Decode(script) + require.NoError(t, err) + require.False(t, valid) + }) + + t.Run("invalid script - extra data after checksig", func(t *testing.T) { + pubKeyBytes := schnorr.SerializePubKey(pubKey1) + script := []byte{txscript.OP_DATA_32} + script = append(script, pubKeyBytes...) + script = append(script, txscript.OP_CHECKSIG) + script = append(script, 0x00) // Extra unwanted byte + + closure := &tree.MultisigClosure{} + valid, err := closure.Decode(script) + require.NoError(t, err) + require.False(t, valid) + }) + + t.Run("witness size", func(t *testing.T) { + closure := &tree.MultisigClosure{ + PubKeys: []*secp256k1.PublicKey{pubKey1, pubKey2}, + } + + require.Equal(t, 128, closure.WitnessSize()) // 64 * 2 bytes + }) + + t.Run("valid 12-of-12 multisig", func(t *testing.T) { + // Generate 12 keys + pubKeys := make([]*secp256k1.PublicKey, 12) + for i := 0; i < 12; i++ { + privKey, err := secp256k1.GeneratePrivateKey() + require.NoError(t, err) + pubKeys[i] = privKey.PubKey() + } + + closure := &tree.MultisigClosure{ + PubKeys: pubKeys, + } + + // Generate script + script, err := closure.Script() + require.NoError(t, err) + + // Test decoding + decodedClosure := &tree.MultisigClosure{} + valid, err := decodedClosure.Decode(script) + require.NoError(t, err) + require.True(t, valid) + require.Equal(t, 12, len(decodedClosure.PubKeys)) + + // Compare all serialized pubkeys + for i := 0; i < 12; i++ { + require.Equal(t, + schnorr.SerializePubKey(pubKeys[i]), + schnorr.SerializePubKey(decodedClosure.PubKeys[i]), + ) + } + + // Verify witness size is correct for 12 signatures + require.Equal(t, 64*12, closure.WitnessSize()) + }) +} + +func TestCSVSigClosure(t *testing.T) { + // Generate test keys + privKey1, err := secp256k1.GeneratePrivateKey() + require.NoError(t, err) + pubKey1 := privKey1.PubKey() + + privKey2, err := secp256k1.GeneratePrivateKey() + require.NoError(t, err) + pubKey2 := privKey2.PubKey() + + t.Run("valid single key CSV", func(t *testing.T) { + csvSig := &tree.CSVSigClosure{ + MultisigClosure: tree.MultisigClosure{ + PubKeys: []*secp256k1.PublicKey{pubKey1}, + }, + Seconds: 1024, + } + + script, err := csvSig.Script() + require.NoError(t, err) + + decodedCSV := &tree.CSVSigClosure{} + valid, err := decodedCSV.Decode(script) + require.NoError(t, err) + require.True(t, valid) + require.Equal(t, uint32(1024), uint32(decodedCSV.Seconds)) + require.Equal(t, 1, len(decodedCSV.PubKeys)) + require.Equal(t, + schnorr.SerializePubKey(pubKey1), + schnorr.SerializePubKey(decodedCSV.PubKeys[0]), + ) + }) + + t.Run("valid 2-of-2 CSV", func(t *testing.T) { + csvSig := &tree.CSVSigClosure{ + MultisigClosure: tree.MultisigClosure{ + PubKeys: []*secp256k1.PublicKey{pubKey1, pubKey2}, + }, + Seconds: 2016, // ~2 weeks + } + + script, err := csvSig.Script() + require.NoError(t, err) + + decodedCSV := &tree.CSVSigClosure{} + valid, err := decodedCSV.Decode(script) + require.NoError(t, err) + require.True(t, valid) + require.Equal(t, uint32(2016), uint32(decodedCSV.Seconds)) + require.Equal(t, 2, len(decodedCSV.PubKeys)) + require.Equal(t, + schnorr.SerializePubKey(pubKey1), + schnorr.SerializePubKey(decodedCSV.PubKeys[0]), + ) + require.Equal(t, + schnorr.SerializePubKey(pubKey2), + schnorr.SerializePubKey(decodedCSV.PubKeys[1]), + ) + }) + + t.Run("invalid empty script", func(t *testing.T) { + csvSig := &tree.CSVSigClosure{} + valid, err := csvSig.Decode([]byte{}) + require.Error(t, err) + require.False(t, valid) + }) + + t.Run("invalid CSV value", func(t *testing.T) { + // Create a script with invalid CSV value + pubKeyBytes := schnorr.SerializePubKey(pubKey1) + script := []byte{txscript.OP_DATA_32} + script = append(script, pubKeyBytes...) + script = append(script, txscript.OP_CHECKSIG) + script = append(script, 0xFF) // Invalid CSV value + + csvSig := &tree.CSVSigClosure{} + valid, err := csvSig.Decode(script) + require.NoError(t, err) + require.False(t, valid) + }) + + t.Run("witness size", func(t *testing.T) { + csvSig := &tree.CSVSigClosure{ + MultisigClosure: tree.MultisigClosure{ + PubKeys: []*secp256k1.PublicKey{pubKey1, pubKey2}, + }, + Seconds: 1024, + } + // Should be same as multisig witness size (64 bytes per signature) + require.Equal(t, 128, csvSig.WitnessSize()) + }) + + t.Run("max timelock", func(t *testing.T) { + csvSig := &tree.CSVSigClosure{ + MultisigClosure: tree.MultisigClosure{ + PubKeys: []*secp256k1.PublicKey{pubKey1}, + }, + Seconds: 65535, // Maximum allowed value + } + + script, err := csvSig.Script() + require.NoError(t, err) + + decodedCSV := &tree.CSVSigClosure{} + valid, err := decodedCSV.Decode(script) + require.NoError(t, err) + require.True(t, valid) + require.Equal(t, uint32(65535), uint32(decodedCSV.Seconds)) + }) +} + +func TestMultisigClosureWitness(t *testing.T) { + // Generate test keys + priv1, err := secp256k1.GeneratePrivateKey() + require.NoError(t, err) + pub1 := priv1.PubKey() + + priv2, err := secp256k1.GeneratePrivateKey() + require.NoError(t, err) + pub2 := priv2.PubKey() + + // Mock control block + controlBlock := []byte("control block") + + testCases := []struct { + name string + closure *tree.MultisigClosure + signatures map[string][]byte + expectError bool + }{ + { + name: "single signature success", + closure: &tree.MultisigClosure{ + PubKeys: []*secp256k1.PublicKey{pub1}, + }, + signatures: map[string][]byte{ + hex.EncodeToString(schnorr.SerializePubKey(pub1)): []byte("signature1"), + }, + expectError: false, + }, + { + name: "multiple signatures success", + closure: &tree.MultisigClosure{ + PubKeys: []*secp256k1.PublicKey{pub1, pub2}, + }, + signatures: map[string][]byte{ + hex.EncodeToString(schnorr.SerializePubKey(pub1)): []byte("signature1"), + hex.EncodeToString(schnorr.SerializePubKey(pub2)): []byte("signature2"), + }, + expectError: false, + }, + { + name: "missing signature", + closure: &tree.MultisigClosure{ + PubKeys: []*secp256k1.PublicKey{pub1, pub2}, + }, + signatures: map[string][]byte{ + hex.EncodeToString(schnorr.SerializePubKey(pub1)): []byte("signature1"), + }, + expectError: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + witness, err := tc.closure.Witness(controlBlock, tc.signatures) + if tc.expectError { + require.Error(t, err) + return + } + + require.NoError(t, err) + + // Total witness stack should be: signatures + script + control block + expectedLen := len(tc.closure.PubKeys) + 2 + require.Equal(t, expectedLen, len(witness)) + + // Verify signatures are in correct order (reverse order of pubkeys) + for i := len(tc.closure.PubKeys) - 1; i >= 0; i-- { + expectedSig := tc.signatures[hex.EncodeToString(schnorr.SerializePubKey(tc.closure.PubKeys[i]))] + witnessIndex := len(witness) - 3 - i + require.Equal(t, expectedSig, witness[:len(witness)-2][witnessIndex]) + } + + // Verify script is present + script, err := tc.closure.Script() + require.NoError(t, err) + require.Equal(t, script, witness[len(witness)-2]) + + // Verify control block is last + require.Equal(t, controlBlock, witness[len(witness)-1]) + }) + } +} + +func TestUnrollClosureWitness(t *testing.T) { + closure := &tree.UnrollClosure{ + LeftKey: secp256k1.NewPublicKey(new(secp256k1.FieldVal), new(secp256k1.FieldVal)), + RightKey: secp256k1.NewPublicKey(new(secp256k1.FieldVal), new(secp256k1.FieldVal)), + LeftAmount: 1000, + RightAmount: 2000, + } + + controlBlock := []byte("control block") + witness, err := closure.Witness(controlBlock, nil) + require.NoError(t, err) + + // Should contain script and control block + require.Equal(t, 2, len(witness)) + + // Verify script is first + script, err := closure.Script() + require.NoError(t, err) + require.Equal(t, script, witness[0]) + + // Verify control block is last + require.Equal(t, controlBlock, witness[1]) +} + +func TestCSVSigClosureWitness(t *testing.T) { + // Generate test keys + priv1, err := secp256k1.GeneratePrivateKey() + require.NoError(t, err) + pub1 := priv1.PubKey() + + // Create test signature + testSig := []byte("signature1") + signatures := map[string][]byte{ + hex.EncodeToString(schnorr.SerializePubKey(pub1)): testSig, + } + + controlBlock := []byte("control block") + + closure := &tree.CSVSigClosure{ + MultisigClosure: tree.MultisigClosure{ + PubKeys: []*secp256k1.PublicKey{pub1}, + }, + Seconds: 144, + } + + witness, err := closure.Witness(controlBlock, signatures) + require.NoError(t, err) + + // Should contain: signature + script + control block + require.Equal(t, 3, len(witness)) + require.Equal(t, testSig, witness[0]) + + script, err := closure.Script() + require.NoError(t, err) + require.Equal(t, script, witness[1]) + require.Equal(t, controlBlock, witness[2]) + + // Test missing signature + _, err = closure.Witness(controlBlock, nil) + require.Error(t, err) +} + +func TestDecodeChecksigAdd(t *testing.T) { + // Generate some test public keys + pubKey1, err := secp256k1.GeneratePrivateKey() + require.NoError(t, err) + pubKey2, err := secp256k1.GeneratePrivateKey() + require.NoError(t, err) + pubKey3, err := secp256k1.GeneratePrivateKey() + require.NoError(t, err) + + pubKeys := []*secp256k1.PublicKey{pubKey1.PubKey(), pubKey2.PubKey(), pubKey3.PubKey()} + + // Create a script for 3-of-3 multisig using CHECKSIGADD + scriptBuilder := txscript.NewScriptBuilder(). + AddData(schnorr.SerializePubKey(pubKeys[0])). + AddOp(txscript.OP_CHECKSIG). + AddData(schnorr.SerializePubKey(pubKeys[1])). + AddOp(txscript.OP_CHECKSIGADD). + AddData(schnorr.SerializePubKey(pubKeys[2])). + AddOp(txscript.OP_CHECKSIGADD). + AddInt64(3). + AddOp(txscript.OP_EQUAL) + + script, err := scriptBuilder.Script() + require.NoError(t, err, "failed to build script") + + // Decode the script + multisigClosure := &tree.MultisigClosure{} + valid, err := multisigClosure.Decode(script) + require.NoError(t, err, "failed to decode script") + require.True(t, valid, "script should be valid") + require.Equal(t, tree.MultisigTypeChecksigAdd, multisigClosure.Type, "expected MultisigTypeChecksigAdd") + require.Equal(t, 3, len(multisigClosure.PubKeys), "expected 3 public keys") +} diff --git a/common/tree/validation.go b/common/tree/validation.go index 975814c..affc6c2 100644 --- a/common/tree/validation.go +++ b/common/tree/validation.go @@ -238,8 +238,8 @@ func validateNodeTransaction( switch c := closure.(type) { case *CSVSigClosure: - isASP := bytes.Equal( - schnorr.SerializePubKey(c.Pubkey), + isASP := len(c.MultisigClosure.PubKeys) == 1 && bytes.Equal( + schnorr.SerializePubKey(c.MultisigClosure.PubKeys[0]), schnorr.SerializePubKey(expectedPublicKeyASP), ) isSweepDelay := int64(c.Seconds) == expectedSequence diff --git a/common/tree/vtxo.go b/common/tree/vtxo.go index b552f0d..a6b8d74 100644 --- a/common/tree/vtxo.go +++ b/common/tree/vtxo.go @@ -1,89 +1,159 @@ package tree import ( + "bytes" "encoding/hex" "fmt" + "math" "github.com/ark-network/ark/common" - "github.com/ark-network/ark/common/descriptor" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/vulpemventures/go-elements/taproot" ) -type VtxoScript common.VtxoScript[elementsTapTree] +var ( + ErrNoExitLeaf = fmt.Errorf("no exit leaf") +) -func ParseVtxoScript(desc string) (VtxoScript, error) { - v := &DefaultVtxoScript{} - // TODO add other type - err := v.FromDescriptor(desc) +type VtxoScript common.VtxoScript[elementsTapTree, *MultisigClosure, *CSVSigClosure] + +func ParseVtxoScript(scripts []string) (VtxoScript, error) { + v := &TapscriptsVtxoScript{} + + err := v.Decode(scripts) return v, err } -/* -* DefaultVtxoScript is the default implementation of VTXO with 2 closures -* - Owner and ASP (forfeit) -* - Owner after t (unilateral exit) - */ -type DefaultVtxoScript struct { - Owner *secp256k1.PublicKey - Asp *secp256k1.PublicKey - ExitDelay uint -} - -func (v *DefaultVtxoScript) ToDescriptor() string { - owner := hex.EncodeToString(schnorr.SerializePubKey(v.Owner)) - - return fmt.Sprintf( - descriptor.DefaultVtxoDescriptorTemplate, - hex.EncodeToString(UnspendableKey().SerializeCompressed()), - owner, - hex.EncodeToString(schnorr.SerializePubKey(v.Asp)), - v.ExitDelay, - owner, - ) -} - -func (v *DefaultVtxoScript) FromDescriptor(desc string) error { - owner, asp, exitDelay, err := descriptor.ParseDefaultVtxoDescriptor(desc) - if err != nil { - return err +func NewDefaultVtxoScript(owner, asp *secp256k1.PublicKey, exitDelay uint) *TapscriptsVtxoScript { + return &TapscriptsVtxoScript{ + []Closure{ + &CSVSigClosure{ + MultisigClosure: MultisigClosure{PubKeys: []*secp256k1.PublicKey{owner}}, + Seconds: exitDelay, + }, + &MultisigClosure{PubKeys: []*secp256k1.PublicKey{owner, asp}}, + }, } +} - v.Owner = owner - v.Asp = asp - v.ExitDelay = exitDelay +// TapscriptsVtxoScript represents a taproot script that contains a list of tapscript leaves +// the key-path is always unspendable +type TapscriptsVtxoScript struct { + Closures []Closure +} + +func (v *TapscriptsVtxoScript) Encode() ([]string, error) { + encoded := make([]string, 0) + for _, closure := range v.Closures { + script, err := closure.Script() + if err != nil { + return nil, err + } + encoded = append(encoded, hex.EncodeToString(script)) + } + return encoded, nil +} + +func (v *TapscriptsVtxoScript) Decode(scripts []string) error { + v.Closures = make([]Closure, 0, len(scripts)) + for _, script := range scripts { + scriptBytes, err := hex.DecodeString(script) + if err != nil { + return err + } + + closure, err := DecodeClosure(scriptBytes) + if err != nil { + return err + } + v.Closures = append(v.Closures, closure) + } return nil } -func (v *DefaultVtxoScript) TapTree() (*secp256k1.PublicKey, elementsTapTree, error) { - redeemClosure := &CSVSigClosure{ - Pubkey: v.Owner, - Seconds: v.ExitDelay, +func (v *TapscriptsVtxoScript) Validate(asp *secp256k1.PublicKey, minExitDelay uint) error { + aspXonly := schnorr.SerializePubKey(asp) + for _, forfeit := range v.ForfeitClosures() { + // must contain asp pubkey + found := false + for _, pubkey := range forfeit.PubKeys { + if bytes.Equal(schnorr.SerializePubKey(pubkey), aspXonly) { + found = true + break + } + } + if !found { + return fmt.Errorf("invalid forfeit closure, ASP pubkey not found") + } } - redeemLeaf, err := redeemClosure.Leaf() + smallestExit, err := v.SmallestExitDelay() if err != nil { - return nil, elementsTapTree{}, err + if err == ErrNoExitLeaf { + return nil + } + return err } - forfeitClosure := &MultisigClosure{ - Pubkey: v.Owner, - AspPubkey: v.Asp, + if smallestExit < minExitDelay { + return fmt.Errorf("exit delay is too short") } - forfeitLeaf, err := forfeitClosure.Leaf() - if err != nil { - return nil, elementsTapTree{}, err + return nil +} + +func (v *TapscriptsVtxoScript) SmallestExitDelay() (uint, error) { + smallest := uint(math.MaxUint32) + + for _, closure := range v.Closures { + if csvClosure, ok := closure.(*CSVSigClosure); ok { + if csvClosure.Seconds < smallest { + smallest = csvClosure.Seconds + } + } } - tapTree := taproot.AssembleTaprootScriptTree( - *redeemLeaf, *forfeitLeaf, - ) + if smallest == math.MaxUint32 { + return 0, ErrNoExitLeaf + } + return smallest, nil +} + +func (v *TapscriptsVtxoScript) ForfeitClosures() []*MultisigClosure { + forfeits := make([]*MultisigClosure, 0) + for _, closure := range v.Closures { + if multisigClosure, ok := closure.(*MultisigClosure); ok { + forfeits = append(forfeits, multisigClosure) + } + } + return forfeits +} + +func (v *TapscriptsVtxoScript) ExitClosures() []*CSVSigClosure { + exits := make([]*CSVSigClosure, 0) + for _, closure := range v.Closures { + if csvClosure, ok := closure.(*CSVSigClosure); ok { + exits = append(exits, csvClosure) + } + } + return exits +} + +func (v *TapscriptsVtxoScript) TapTree() (*secp256k1.PublicKey, elementsTapTree, error) { + leaves := make([]taproot.TapElementsLeaf, 0, len(v.Closures)) + for _, closure := range v.Closures { + leaf, err := closure.Script() + if err != nil { + return nil, elementsTapTree{}, err + } + leaves = append(leaves, taproot.NewBaseTapElementsLeaf(leaf)) + } + + tapTree := taproot.AssembleTaprootScriptTree(leaves...) root := tapTree.RootNode.TapHash() - taprootKey := taproot.ComputeTaprootOutputKey(UnspendableKey(), root[:]) return taprootKey, elementsTapTree{tapTree}, nil @@ -111,9 +181,15 @@ func (b elementsTapTree) GetTaprootMerkleProof(leafhash chainhash.Hash) (*common return nil, err } + closure, err := DecodeClosure(proof.Script) + if err != nil { + return nil, err + } + return &common.TaprootMerkleProof{ ControlBlock: controlBlockBytes, Script: proof.Script, + WitnessSize: closure.WitnessSize(), }, nil } diff --git a/common/vtxo.go b/common/vtxo.go index aa41420..c961224 100644 --- a/common/vtxo.go +++ b/common/vtxo.go @@ -14,6 +14,7 @@ var ( type TaprootMerkleProof struct { ControlBlock []byte Script []byte + WitnessSize int } // TaprootTree is an interface wrapping the methods needed to spend a vtxo taproot contract @@ -30,14 +31,15 @@ It may also contain others closures implementing specific use cases. VtxoScript abstracts the taproot complexity behind vtxo contracts. it is compiled, transferred and parsed using descriptor string. - -default vtxo script = tr(_,{ and(pk(USER), pk(ASP)), and(older(T), pk(USER)) }) -reversible vtxo script = tr(_,{ { and(pk(SENDER), pk(ASP)), and(older(T), pk(SENDER)) }, { and(pk(RECEIVER), pk(ASP) } }) */ -type VtxoScript[T TaprootTree] interface { +type VtxoScript[T TaprootTree, F interface{}, E interface{}] interface { + Validate(server *secp256k1.PublicKey, minExitDelay uint) error TapTree() (taprootKey *secp256k1.PublicKey, taprootScriptTree T, err error) - ToDescriptor() string - FromDescriptor(descriptor string) error + Encode() ([]string, error) + Decode(scripts []string) error + SmallestExitDelay() (uint, error) + ForfeitClosures() []F + ExitClosures() []E } // BiggestLeafMerkleProof returns the leaf with the biggest witness size (for fee estimation) diff --git a/pkg/client-sdk/client/client.go b/pkg/client-sdk/client/client.go index 86b3996..c8e2273 100644 --- a/pkg/client-sdk/client/client.go +++ b/pkg/client-sdk/client/client.go @@ -89,7 +89,7 @@ func (o Outpoint) Equals(other Outpoint) bool { type Input struct { Outpoint - Descriptor string + Tapscripts []string } type AsyncPaymentInput struct { @@ -129,9 +129,9 @@ func (v Vtxo) Address(asp *secp256k1.PublicKey, net common.Network) (string, err return a.Encode() } -type DescriptorVtxo struct { +type TapscriptsVtxo struct { Vtxo - Descriptor string + Tapscripts []string } type Output struct { diff --git a/pkg/client-sdk/client/grpc/types.go b/pkg/client-sdk/client/grpc/types.go index cd9f8f7..5dc7073 100644 --- a/pkg/client-sdk/client/grpc/types.go +++ b/pkg/client-sdk/client/grpc/types.go @@ -144,7 +144,11 @@ func toProtoInput(i client.Input) *arkv1.Input { Txid: i.Txid, Vout: i.VOut, }, - Descriptor_: i.Descriptor, + TaprootTree: &arkv1.Input_Tapscripts{ + Tapscripts: &arkv1.Tapscripts{ + Scripts: i.Tapscripts, + }, + }, } } diff --git a/pkg/client-sdk/client/rest/client.go b/pkg/client-sdk/client/rest/client.go index a934d4c..8592c61 100644 --- a/pkg/client-sdk/client/rest/client.go +++ b/pkg/client-sdk/client/rest/client.go @@ -118,7 +118,9 @@ func (a *restClient) RegisterInputsForNextRound( Txid: i.Txid, Vout: int64(i.VOut), }, - Descriptor: i.Descriptor, + Tapscripts: &models.V1Tapscripts{ + Scripts: i.Tapscripts, + }, }) } body := &models.V1RegisterInputsForNextRoundRequest{ @@ -402,7 +404,9 @@ func (a *restClient) CreatePayment( Txid: i.Input.Txid, Vout: int64(i.VOut), }, - Descriptor: i.Input.Descriptor, + Tapscripts: &models.V1Tapscripts{ + Scripts: i.Input.Tapscripts, + }, }, ForfeitLeafHash: i.ForfeitLeafHash.String(), }) diff --git a/pkg/client-sdk/client/rest/service/models/v1_get_boarding_address_response.go b/pkg/client-sdk/client/rest/service/models/v1_get_boarding_address_response.go index 85152f8..df31276 100644 --- a/pkg/client-sdk/client/rest/service/models/v1_get_boarding_address_response.go +++ b/pkg/client-sdk/client/rest/service/models/v1_get_boarding_address_response.go @@ -8,6 +8,7 @@ package models import ( "context" + "github.com/go-openapi/errors" "github.com/go-openapi/strfmt" "github.com/go-openapi/swag" ) @@ -22,15 +23,76 @@ type V1GetBoardingAddressResponse struct { // descriptor Descriptor string `json:"descriptor,omitempty"` + + // tapscripts + Tapscripts *V1Tapscripts `json:"tapscripts,omitempty"` } // Validate validates this v1 get boarding address response func (m *V1GetBoardingAddressResponse) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateTapscripts(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } return nil } -// ContextValidate validates this v1 get boarding address response based on context it is used +func (m *V1GetBoardingAddressResponse) validateTapscripts(formats strfmt.Registry) error { + if swag.IsZero(m.Tapscripts) { // not required + return nil + } + + if m.Tapscripts != nil { + if err := m.Tapscripts.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("tapscripts") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("tapscripts") + } + return err + } + } + + return nil +} + +// ContextValidate validate this v1 get boarding address response based on the context it is used func (m *V1GetBoardingAddressResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateTapscripts(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *V1GetBoardingAddressResponse) contextValidateTapscripts(ctx context.Context, formats strfmt.Registry) error { + + if m.Tapscripts != nil { + + if swag.IsZero(m.Tapscripts) { // not required + return nil + } + + if err := m.Tapscripts.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("tapscripts") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("tapscripts") + } + return err + } + } + return nil } diff --git a/pkg/client-sdk/client/rest/service/models/v1_input.go b/pkg/client-sdk/client/rest/service/models/v1_input.go index d2e9647..cb02f96 100644 --- a/pkg/client-sdk/client/rest/service/models/v1_input.go +++ b/pkg/client-sdk/client/rest/service/models/v1_input.go @@ -23,6 +23,9 @@ type V1Input struct { // outpoint Outpoint *V1Outpoint `json:"outpoint,omitempty"` + + // tapscripts + Tapscripts *V1Tapscripts `json:"tapscripts,omitempty"` } // Validate validates this v1 input @@ -33,6 +36,10 @@ func (m *V1Input) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateTapscripts(formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -58,6 +65,25 @@ func (m *V1Input) validateOutpoint(formats strfmt.Registry) error { return nil } +func (m *V1Input) validateTapscripts(formats strfmt.Registry) error { + if swag.IsZero(m.Tapscripts) { // not required + return nil + } + + if m.Tapscripts != nil { + if err := m.Tapscripts.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("tapscripts") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("tapscripts") + } + return err + } + } + + return nil +} + // ContextValidate validate this v1 input based on the context it is used func (m *V1Input) ContextValidate(ctx context.Context, formats strfmt.Registry) error { var res []error @@ -66,6 +92,10 @@ func (m *V1Input) ContextValidate(ctx context.Context, formats strfmt.Registry) res = append(res, err) } + if err := m.contextValidateTapscripts(ctx, formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -93,6 +123,27 @@ func (m *V1Input) contextValidateOutpoint(ctx context.Context, formats strfmt.Re return nil } +func (m *V1Input) contextValidateTapscripts(ctx context.Context, formats strfmt.Registry) error { + + if m.Tapscripts != nil { + + if swag.IsZero(m.Tapscripts) { // not required + return nil + } + + if err := m.Tapscripts.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("tapscripts") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("tapscripts") + } + return err + } + } + + return nil +} + // MarshalBinary interface implementation func (m *V1Input) MarshalBinary() ([]byte, error) { if m == nil { diff --git a/pkg/client-sdk/client/rest/service/models/v1_ownership_proof.go b/pkg/client-sdk/client/rest/service/models/v1_ownership_proof.go index f2301c3..c5cd3da 100644 --- a/pkg/client-sdk/client/rest/service/models/v1_ownership_proof.go +++ b/pkg/client-sdk/client/rest/service/models/v1_ownership_proof.go @@ -12,7 +12,7 @@ import ( "github.com/go-openapi/swag" ) -// V1OwnershipProof This message is used to prove to the ASP that the user controls the vtxo without revealing the whole VTXO descriptor. +// V1OwnershipProof This message is used to prove to the ASP that the user controls the vtxo without revealing the whole VTXO taproot tree. // // swagger:model v1OwnershipProof type V1OwnershipProof struct { diff --git a/pkg/client-sdk/client/rest/service/models/v1_tapscripts.go b/pkg/client-sdk/client/rest/service/models/v1_tapscripts.go new file mode 100644 index 0000000..ededc5a --- /dev/null +++ b/pkg/client-sdk/client/rest/service/models/v1_tapscripts.go @@ -0,0 +1,50 @@ +// 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" +) + +// V1Tapscripts v1 tapscripts +// +// swagger:model v1Tapscripts +type V1Tapscripts struct { + + // scripts + Scripts []string `json:"scripts"` +} + +// Validate validates this v1 tapscripts +func (m *V1Tapscripts) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this v1 tapscripts based on context it is used +func (m *V1Tapscripts) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *V1Tapscripts) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *V1Tapscripts) UnmarshalBinary(b []byte) error { + var res V1Tapscripts + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/client-sdk/covenant_client.go b/pkg/client-sdk/covenant_client.go index 58f25fa..e27c382 100644 --- a/pkg/client-sdk/covenant_client.go +++ b/pkg/client-sdk/covenant_client.go @@ -22,7 +22,6 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcwallet/waddrmgr" - "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/lightningnetwork/lnd/lnwallet/chainfee" log "github.com/sirupsen/logrus" "github.com/vulpemventures/go-elements/address" @@ -518,7 +517,7 @@ func (a *covenantArkClient) CollaborativeRedeem( }, } - vtxos := make([]client.DescriptorVtxo, 0) + vtxos := make([]client.TapscriptsVtxo, 0) spendableVtxos, err := a.getVtxos(ctx, false, nil) if err != nil { return "", err @@ -532,9 +531,9 @@ func (a *covenantArkClient) CollaborativeRedeem( } if vtxoAddr == offchainAddr.Address { - vtxos = append(vtxos, client.DescriptorVtxo{ + vtxos = append(vtxos, client.TapscriptsVtxo{ Vtxo: v, - Descriptor: offchainAddr.Descriptor, + Tapscripts: offchainAddr.Tapscripts, }) } } @@ -572,7 +571,7 @@ func (a *covenantArkClient) CollaborativeRedeem( Txid: coin.Txid, VOut: coin.VOut, }, - Descriptor: coin.Descriptor, + Tapscripts: coin.Tapscripts, }) } for _, coin := range selectedBoardingUtxos { @@ -581,7 +580,7 @@ func (a *covenantArkClient) CollaborativeRedeem( Txid: coin.Txid, VOut: coin.VOut, }, - Descriptor: coin.Descriptor, + Tapscripts: coin.Tapscripts, }) } @@ -661,7 +660,7 @@ func (a *covenantArkClient) getAllBoardingUtxos(ctx context.Context) ([]types.Ut VOut: uint32(i), Amount: vout.Amount, CreatedAt: createdAt, - Descriptor: addr.Descriptor, + Tapscripts: addr.Tapscripts, }) } } @@ -680,17 +679,14 @@ func (a *covenantArkClient) getClaimableBoardingUtxos(ctx context.Context, opts claimable := make([]types.Utxo, 0) for _, addr := range boardingAddrs { - boardingScript, err := tree.ParseVtxoScript(addr.Descriptor) + boardingScript, err := tree.ParseVtxoScript(addr.Tapscripts) if err != nil { return nil, err } - var boardingTimeout uint - - if defaultVtxo, ok := boardingScript.(*tree.DefaultVtxoScript); ok { - boardingTimeout = defaultVtxo.ExitDelay - } else { - return nil, fmt.Errorf("unsupported boarding descriptor: %s", addr.Descriptor) + boardingTimeout, err := boardingScript.SmallestExitDelay() + if err != nil { + return nil, err } boardingUtxos, err := a.explorer.GetUtxos(addr.Address) @@ -719,7 +715,7 @@ func (a *covenantArkClient) getClaimableBoardingUtxos(ctx context.Context, opts } } - u := utxo.ToUtxo(boardingTimeout, addr.Descriptor) + u := utxo.ToUtxo(boardingTimeout, addr.Tapscripts) if u.SpendableAt.Before(now) { continue } @@ -929,7 +925,7 @@ func (a *covenantArkClient) sendOffchain( return "", fmt.Errorf("no offchain addresses found") } - vtxos := make([]client.DescriptorVtxo, 0) + vtxos := make([]client.TapscriptsVtxo, 0) spendableVtxos, err := a.getVtxos(ctx, withExpiryCoinselect, nil) if err != nil { @@ -944,9 +940,9 @@ func (a *covenantArkClient) sendOffchain( } if vtxoAddr == offchainAddr.Address { - vtxos = append(vtxos, client.DescriptorVtxo{ + vtxos = append(vtxos, client.TapscriptsVtxo{ Vtxo: v, - Descriptor: offchainAddr.Descriptor, + Tapscripts: offchainAddr.Tapscripts, }) } } @@ -958,7 +954,7 @@ func (a *covenantArkClient) sendOffchain( } var selectedBoardingCoins []types.Utxo - var selectedCoins []client.DescriptorVtxo + var selectedCoins []client.TapscriptsVtxo var changeAmount uint64 // if no receivers, self send all selected coins @@ -1009,7 +1005,7 @@ func (a *covenantArkClient) sendOffchain( Txid: coin.Txid, VOut: coin.VOut, }, - Descriptor: coin.Descriptor, + Tapscripts: coin.Tapscripts, }) } for _, coin := range selectedBoardingCoins { @@ -1018,7 +1014,7 @@ func (a *covenantArkClient) sendOffchain( Txid: coin.Txid, VOut: coin.VOut, }, - Descriptor: coin.Descriptor, + Tapscripts: coin.Tapscripts, }) } @@ -1057,19 +1053,33 @@ func (a *covenantArkClient) addInputs( return err } - vtxoScript, err := tree.ParseVtxoScript(offchain.Descriptor) + vtxoScript, err := tree.ParseVtxoScript(offchain.Tapscripts) if err != nil { return err } - var userPubkey, aspPubkey *secp256k1.PublicKey + forfeitClosure := vtxoScript.ForfeitClosures()[0] - switch s := vtxoScript.(type) { - case *tree.DefaultVtxoScript: - userPubkey = s.Owner - aspPubkey = s.Asp - default: - return fmt.Errorf("unsupported vtxo script: %T", s) + forfeitScript, err := forfeitClosure.Script() + if err != nil { + return err + } + + forfeitLeaf := taproot.NewBaseTapElementsLeaf(forfeitScript) + + _, taprootTree, err := vtxoScript.TapTree() + if err != nil { + return err + } + + leafProof, err := taprootTree.GetTaprootMerkleProof(forfeitLeaf.TapHash()) + if err != nil { + return err + } + + controlBlock, err := taproot.ParseControlBlock(leafProof.Script) + if err != nil { + return err } for _, utxo := range utxos { @@ -1088,37 +1098,6 @@ func (a *covenantArkClient) addInputs( return err } - vtxoScript := &tree.DefaultVtxoScript{ - Owner: userPubkey, - Asp: aspPubkey, - ExitDelay: utxo.Delay, - } - - forfeitClosure := &tree.MultisigClosure{ - Pubkey: userPubkey, - AspPubkey: aspPubkey, - } - - forfeitLeaf, err := forfeitClosure.Leaf() - if err != nil { - return err - } - - _, taprootTree, err := vtxoScript.TapTree() - if err != nil { - return err - } - - leafProof, err := taprootTree.GetTaprootMerkleProof(forfeitLeaf.TapHash()) - if err != nil { - return err - } - - controlBlock, err := taproot.ParseControlBlock(leafProof.Script) - if err != nil { - return err - } - inputIndex := len(updater.Pset.Inputs) - 1 if err := updater.AddInTapLeafScript( @@ -1138,7 +1117,7 @@ func (a *covenantArkClient) addInputs( func (a *covenantArkClient) handleRoundStream( ctx context.Context, paymentID string, - vtxosToSign []client.DescriptorVtxo, + vtxosToSign []client.TapscriptsVtxo, boardingUtxos []types.Utxo, receivers []client.Output, ) (string, error) { @@ -1202,7 +1181,7 @@ func (a *covenantArkClient) handleRoundStream( func (a *covenantArkClient) handleRoundFinalization( ctx context.Context, event client.RoundFinalizationEvent, - vtxos []client.DescriptorVtxo, + vtxos []client.TapscriptsVtxo, boardingUtxos []types.Utxo, receivers []client.Output, ) (signedForfeits []string, signedRoundTx string, err error) { @@ -1233,28 +1212,20 @@ func (a *covenantArkClient) handleRoundFinalization( } for _, boardingUtxo := range boardingUtxos { - boardingVtxoScript, err := tree.ParseVtxoScript(boardingUtxo.Descriptor) + boardingVtxoScript, err := tree.ParseVtxoScript(boardingUtxo.Tapscripts) if err != nil { return nil, "", err } - var forfeitClosure tree.Closure + forfeitClosure := boardingVtxoScript.ForfeitClosures()[0] - switch s := boardingVtxoScript.(type) { - case *tree.DefaultVtxoScript: - forfeitClosure = &tree.MultisigClosure{ - Pubkey: s.Owner, - AspPubkey: a.AspPubkey, - } - default: - return nil, "", fmt.Errorf("unsupported boarding descriptor: %s", boardingUtxo.Descriptor) - } - - forfeitLeaf, err := forfeitClosure.Leaf() + forfeitScript, err := forfeitClosure.Script() if err != nil { return nil, "", err } + forfeitLeaf := taproot.NewBaseTapElementsLeaf(forfeitScript) + _, taprootTree, err := boardingVtxoScript.TapTree() if err != nil { return nil, "", err @@ -1430,7 +1401,7 @@ func (a *covenantArkClient) validateOffChainReceiver( func (a *covenantArkClient) createAndSignForfeits( ctx context.Context, - vtxosToSign []client.DescriptorVtxo, + vtxosToSign []client.TapscriptsVtxo, connectors []string, feeRate chainfee.SatPerKVByte, ) ([]string, error) { @@ -1452,7 +1423,7 @@ func (a *covenantArkClient) createAndSignForfeits( } for _, vtxo := range vtxosToSign { - vtxoScript, err := tree.ParseVtxoScript(vtxo.Descriptor) + vtxoScript, err := tree.ParseVtxoScript(vtxo.Tapscripts) if err != nil { return nil, err } @@ -1472,25 +1443,15 @@ func (a *covenantArkClient) createAndSignForfeits( TxIndex: vtxo.VOut, } - var forfeitClosure tree.Closure - var witnessSize int + forfeitClosure := vtxoScript.ForfeitClosures()[0] - switch s := vtxoScript.(type) { - case *tree.DefaultVtxoScript: - forfeitClosure = &tree.MultisigClosure{ - Pubkey: s.Owner, - AspPubkey: a.AspPubkey, - } - witnessSize = 64 * 2 - default: - return nil, fmt.Errorf("unsupported vtxo script: %T", s) - } - - forfeitLeaf, err := forfeitClosure.Leaf() + forfeitScript, err := forfeitClosure.Script() if err != nil { return nil, err } + forfeitLeaf := taproot.NewBaseTapElementsLeaf(forfeitScript) + leafProof, err := vtxoTapTree.GetTaprootMerkleProof(forfeitLeaf.TapHash()) if err != nil { return nil, err @@ -1512,7 +1473,7 @@ func (a *covenantArkClient) createAndSignForfeits( RevealedScript: leafProof.Script, ControlBlock: &ctrlBlock.ControlBlock, }, - witnessSize, + forfeitClosure.WitnessSize(), txscript.WitnessV0PubKeyHashTy, ) if err != nil { @@ -1567,19 +1528,14 @@ func (a *covenantArkClient) coinSelectOnchain( fetchedUtxos := make([]types.Utxo, 0) for _, addr := range boardingAddrs { - boardingDescriptor := addr.Descriptor - - boardingScript, err := tree.ParseVtxoScript(boardingDescriptor) + boardingScript, err := tree.ParseVtxoScript(addr.Tapscripts) if err != nil { return nil, 0, err } - var boardingTimeout uint - - if defaultVtxo, ok := boardingScript.(*tree.DefaultVtxoScript); ok { - boardingTimeout = defaultVtxo.ExitDelay - } else { - return nil, 0, fmt.Errorf("unsupported boarding descriptor: %s", boardingDescriptor) + boardingTimeout, err := boardingScript.SmallestExitDelay() + if err != nil { + return nil, 0, err } utxos, err := a.explorer.GetUtxos(addr.Address) @@ -1588,7 +1544,7 @@ func (a *covenantArkClient) coinSelectOnchain( } for _, utxo := range utxos { - u := utxo.ToUtxo(boardingTimeout, addr.Descriptor) + u := utxo.ToUtxo(boardingTimeout, addr.Tapscripts) if u.SpendableAt.Before(now) { fetchedUtxos = append(fetchedUtxos, u) } @@ -1624,7 +1580,7 @@ func (a *covenantArkClient) coinSelectOnchain( } for _, utxo := range utxos { - u := utxo.ToUtxo(uint(a.UnilateralExitDelay), addr.Descriptor) + u := utxo.ToUtxo(uint(a.UnilateralExitDelay), addr.Tapscripts) if u.SpendableAt.Before(now) { fetchedUtxos = append(fetchedUtxos, u) } diff --git a/pkg/client-sdk/covenantless_client.go b/pkg/client-sdk/covenantless_client.go index 4b913a5..10a5301 100644 --- a/pkg/client-sdk/covenantless_client.go +++ b/pkg/client-sdk/covenantless_client.go @@ -857,7 +857,7 @@ func (a *covenantlessArkClient) CollaborativeRedeem( }, } - vtxos := make([]client.DescriptorVtxo, 0) + vtxos := make([]client.TapscriptsVtxo, 0) spendableVtxos, err := a.getVtxos(ctx, nil) if err != nil { return "", err @@ -871,9 +871,9 @@ func (a *covenantlessArkClient) CollaborativeRedeem( } if vtxoAddr == offchainAddr.Address { - vtxos = append(vtxos, client.DescriptorVtxo{ + vtxos = append(vtxos, client.TapscriptsVtxo{ Vtxo: v, - Descriptor: offchainAddr.Descriptor, + Tapscripts: offchainAddr.Tapscripts, }) } } @@ -911,7 +911,7 @@ func (a *covenantlessArkClient) CollaborativeRedeem( Txid: coin.Txid, VOut: coin.VOut, }, - Descriptor: coin.Descriptor, + Tapscripts: coin.Tapscripts, }) } for _, coin := range selectedBoardingCoins { @@ -920,7 +920,7 @@ func (a *covenantlessArkClient) CollaborativeRedeem( Txid: coin.Txid, VOut: coin.VOut, }, - Descriptor: coin.Descriptor, + Tapscripts: coin.Tapscripts, }) } @@ -1004,7 +1004,7 @@ func (a *covenantlessArkClient) SendAsync( sumOfReceivers += receiver.Amount() } - vtxos := make([]client.DescriptorVtxo, 0) + vtxos := make([]client.TapscriptsVtxo, 0) opts := &CoinSelectOptions{ WithExpirySorting: withExpiryCoinselect, } @@ -1021,9 +1021,9 @@ func (a *covenantlessArkClient) SendAsync( } if vtxoAddr == offchainAddr.Address { - vtxos = append(vtxos, client.DescriptorVtxo{ + vtxos = append(vtxos, client.TapscriptsVtxo{ Vtxo: v, - Descriptor: offchainAddr.Descriptor, + Tapscripts: offchainAddr.Tapscripts, }) } } @@ -1048,35 +1048,27 @@ func (a *covenantlessArkClient) SendAsync( inputs := make([]client.AsyncPaymentInput, 0, len(selectedCoins)) for _, coin := range selectedCoins { - vtxoScript, err := bitcointree.ParseVtxoScript(coin.Descriptor) + vtxoScript, err := bitcointree.ParseVtxoScript(coin.Tapscripts) if err != nil { return "", err } - var forfeitClosure bitcointree.Closure + forfeitClosure := vtxoScript.ForfeitClosures()[0] - switch s := vtxoScript.(type) { - case *bitcointree.DefaultVtxoScript: - forfeitClosure = &bitcointree.MultisigClosure{ - Pubkey: s.Owner, - AspPubkey: s.Asp, - } - default: - return "", fmt.Errorf("unsupported vtxo script: %T", s) - } - - forfeitLeaf, err := forfeitClosure.Leaf() + forfeitScript, err := forfeitClosure.Script() if err != nil { return "", err } + forfeitLeaf := txscript.NewBaseTapLeaf(forfeitScript) + inputs = append(inputs, client.AsyncPaymentInput{ Input: client.Input{ Outpoint: client.Outpoint{ Txid: coin.Txid, VOut: coin.VOut, }, - Descriptor: coin.Descriptor, + Tapscripts: coin.Tapscripts, }, ForfeitLeafHash: forfeitLeaf.TapHash(), }) @@ -1158,7 +1150,7 @@ func (a *covenantlessArkClient) SetNostrNotificationRecipient(ctx context.Contex return err } - descriptorVtxos := make([]client.DescriptorVtxo, 0) + descriptorVtxos := make([]client.TapscriptsVtxo, 0) for _, offchainAddr := range offchainAddrs { for _, vtxo := range spendableVtxos { vtxoAddr, err := vtxo.Address(a.AspPubkey, a.Network) @@ -1167,9 +1159,9 @@ func (a *covenantlessArkClient) SetNostrNotificationRecipient(ctx context.Contex } if vtxoAddr == offchainAddr.Address { - descriptorVtxos = append(descriptorVtxos, client.DescriptorVtxo{ + descriptorVtxos = append(descriptorVtxos, client.TapscriptsVtxo{ Vtxo: vtxo, - Descriptor: offchainAddr.Descriptor, + Tapscripts: offchainAddr.Tapscripts, }) } } @@ -1187,35 +1179,24 @@ func (a *covenantlessArkClient) SetNostrNotificationRecipient(ctx context.Contex } // validate the vtxo script type - vtxoScript, err := bitcointree.ParseVtxoScript(v.Descriptor) + vtxoScript, err := bitcointree.ParseVtxoScript(v.Tapscripts) if err != nil { return err } - var forfeitClosure bitcointree.Closure - var signingPubkey string - - if defaultVtxoScript, ok := vtxoScript.(*bitcointree.DefaultVtxoScript); ok { - forfeitClosure = &bitcointree.MultisigClosure{ - Pubkey: defaultVtxoScript.Owner, - AspPubkey: defaultVtxoScript.Asp, - } - - signingPubkey = hex.EncodeToString(schnorr.SerializePubKey(defaultVtxoScript.Owner)) - } else { - return fmt.Errorf("unsupported vtxo script: %T", vtxoScript) - } + forfeitClosure := vtxoScript.ForfeitClosures()[0] _, tapTree, err := vtxoScript.TapTree() if err != nil { return err } - forfeitLeaf, err := forfeitClosure.Leaf() + forfeitScript, err := forfeitClosure.Script() if err != nil { return err } + forfeitLeaf := txscript.NewBaseTapLeaf(forfeitScript) merkleProof, err := tapTree.GetTaprootMerkleProof(forfeitLeaf.TapHash()) if err != nil { return err @@ -1236,7 +1217,7 @@ func (a *covenantlessArkClient) SetNostrNotificationRecipient(ctx context.Contex outpointBytes := append(txhash[:], voutBytes...) sigMsg := sha256.Sum256(outpointBytes) - sig, err := a.wallet.SignMessage(ctx, sigMsg[:], signingPubkey) + sig, err := a.wallet.SignMessage(ctx, sigMsg[:]) if err != nil { return err } @@ -1434,7 +1415,7 @@ func (a *covenantlessArkClient) sendOffchain( return "", fmt.Errorf("no offchain addresses found") } - vtxos := make([]client.DescriptorVtxo, 0) + vtxos := make([]client.TapscriptsVtxo, 0) opts := &CoinSelectOptions{ WithExpirySorting: withExpiryCoinselect} spendableVtxos, err := a.getVtxos(ctx, opts) @@ -1450,9 +1431,9 @@ func (a *covenantlessArkClient) sendOffchain( } if vtxoAddr == offchainAddr.Address { - vtxos = append(vtxos, client.DescriptorVtxo{ + vtxos = append(vtxos, client.TapscriptsVtxo{ Vtxo: v, - Descriptor: offchainAddr.Descriptor, + Tapscripts: offchainAddr.Tapscripts, }) } } @@ -1464,7 +1445,7 @@ func (a *covenantlessArkClient) sendOffchain( } var selectedBoardingCoins []types.Utxo - var selectedCoins []client.DescriptorVtxo + var selectedCoins []client.TapscriptsVtxo var changeAmount uint64 // if no receivers, self send all selected coins @@ -1513,7 +1494,7 @@ func (a *covenantlessArkClient) sendOffchain( Txid: coin.Txid, VOut: coin.VOut, }, - Descriptor: coin.Descriptor, + Tapscripts: coin.Tapscripts, }) } for _, boardingUtxo := range selectedBoardingCoins { @@ -1522,7 +1503,7 @@ func (a *covenantlessArkClient) sendOffchain( Txid: boardingUtxo.Txid, VOut: boardingUtxo.VOut, }, - Descriptor: boardingUtxo.Descriptor, + Tapscripts: boardingUtxo.Tapscripts, }) } @@ -1567,21 +1548,11 @@ func (a *covenantlessArkClient) addInputs( return err } - vtxoScript, err := tree.ParseVtxoScript(offchain.Descriptor) + vtxoScript, err := bitcointree.ParseVtxoScript(offchain.Tapscripts) if err != nil { return err } - var userPubkey, aspPubkey *secp256k1.PublicKey - - switch s := vtxoScript.(type) { - case *tree.DefaultVtxoScript: - userPubkey = s.Owner - aspPubkey = s.Asp - default: - return fmt.Errorf("unsupported vtxo script: %T", s) - } - for _, utxo := range utxos { previousHash, err := chainhash.NewHashFromStr(utxo.Txid) if err != nil { @@ -1601,18 +1572,14 @@ func (a *covenantlessArkClient) addInputs( Sequence: sequence, }) - vtxoScript := &bitcointree.DefaultVtxoScript{ - Owner: userPubkey, - Asp: aspPubkey, - ExitDelay: utxo.Delay, + exitClosures := vtxoScript.ExitClosures() + if len(exitClosures) <= 0 { + return fmt.Errorf("no exit closures found") } - exitClosure := &bitcointree.CSVSigClosure{ - Pubkey: userPubkey, - Seconds: uint(utxo.Delay), - } + exitClosure := exitClosures[0] - exitLeaf, err := exitClosure.Leaf() + exitScript, err := exitClosure.Script() if err != nil { return err } @@ -1622,6 +1589,7 @@ func (a *covenantlessArkClient) addInputs( return err } + exitLeaf := txscript.NewBaseTapLeaf(exitScript) leafProof, err := taprootTree.GetTaprootMerkleProof(exitLeaf.TapHash()) if err != nil { return fmt.Errorf("failed to get taproot merkle proof: %s", err) @@ -1644,7 +1612,7 @@ func (a *covenantlessArkClient) addInputs( func (a *covenantlessArkClient) handleRoundStream( ctx context.Context, paymentID string, - vtxosToSign []client.DescriptorVtxo, + vtxosToSign []client.TapscriptsVtxo, boardingUtxos []types.Utxo, receivers []client.Output, roundEphemeralKey *secp256k1.PrivateKey, @@ -1763,12 +1731,12 @@ 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), + sweepClosure := tree.CSVSigClosure{ + MultisigClosure: tree.MultisigClosure{PubKeys: []*secp256k1.PublicKey{a.AspPubkey}}, + Seconds: uint(a.RoundLifetime), } - sweepTapLeaf, err := sweepClosure.Leaf() + script, err := sweepClosure.Script() if err != nil { return } @@ -1781,7 +1749,8 @@ func (a *covenantlessArkClient) handleRoundSigningStarted( sharedOutput := roundTx.UnsignedTx.TxOut[0] sharedOutputValue := sharedOutput.Value - sweepTapTree := txscript.AssembleTaprootScriptTree(*sweepTapLeaf) + sweepTapLeaf := txscript.NewBaseTapLeaf(script) + sweepTapTree := txscript.AssembleTaprootScriptTree(sweepTapLeaf) root := sweepTapTree.RootNode.TapHash() signerSession = bitcointree.NewTreeSignerSession( @@ -1838,7 +1807,7 @@ func (a *covenantlessArkClient) handleRoundSigningNoncesGenerated( func (a *covenantlessArkClient) handleRoundFinalization( ctx context.Context, event client.RoundFinalizationEvent, - vtxos []client.DescriptorVtxo, + vtxos []client.TapscriptsVtxo, boardingUtxos []types.Utxo, receivers []client.Output, ) ([]string, string, error) { @@ -1870,27 +1839,20 @@ func (a *covenantlessArkClient) handleRoundFinalization( } for _, boardingUtxo := range boardingUtxos { - boardingVtxoScript, err := bitcointree.ParseVtxoScript(boardingUtxo.Descriptor) + boardingVtxoScript, err := bitcointree.ParseVtxoScript(boardingUtxo.Tapscripts) if err != nil { return nil, "", err } - var myPubkey *secp256k1.PublicKey - - switch v := boardingVtxoScript.(type) { - case *bitcointree.DefaultVtxoScript: - myPubkey = v.Owner - default: - return nil, "", fmt.Errorf("unsupported boarding descriptor: %s", boardingUtxo.Descriptor) - } - // add tapscript leaf - forfeitClosure := &bitcointree.MultisigClosure{ - Pubkey: myPubkey, - AspPubkey: a.AspPubkey, + forfeitClosures := boardingVtxoScript.ForfeitClosures() + if len(forfeitClosures) <= 0 { + return nil, "", fmt.Errorf("no forfeit closures found") } - forfeitLeaf, err := forfeitClosure.Leaf() + forfeitClosure := forfeitClosures[0] + + forfeitScript, err := forfeitClosure.Script() if err != nil { return nil, "", err } @@ -1900,6 +1862,7 @@ func (a *covenantlessArkClient) handleRoundFinalization( return nil, "", err } + forfeitLeaf := txscript.NewBaseTapLeaf(forfeitScript) forfeitProof, err := taprootTree.GetTaprootMerkleProof(forfeitLeaf.TapHash()) if err != nil { return nil, "", fmt.Errorf("failed to get taproot merkle proof for boarding utxo: %s", err) @@ -2070,7 +2033,7 @@ func (a *covenantlessArkClient) validateOffChainReceiver( func (a *covenantlessArkClient) createAndSignForfeits( ctx context.Context, - vtxosToSign []client.DescriptorVtxo, + vtxosToSign []client.TapscriptsVtxo, connectors []string, feeRate chainfee.SatPerKVByte, ) ([]string, error) { @@ -2102,7 +2065,7 @@ func (a *covenantlessArkClient) createAndSignForfeits( } for _, vtxo := range vtxosToSign { - vtxoScript, err := bitcointree.ParseVtxoScript(vtxo.Descriptor) + vtxoScript, err := bitcointree.ParseVtxoScript(vtxo.Tapscripts) if err != nil { return nil, err } @@ -2127,25 +2090,19 @@ func (a *covenantlessArkClient) createAndSignForfeits( Index: vtxo.VOut, } - var forfeitClosure bitcointree.Closure - var witnessSize int - - switch v := vtxoScript.(type) { - case *bitcointree.DefaultVtxoScript: - forfeitClosure = &bitcointree.MultisigClosure{ - Pubkey: v.Owner, - AspPubkey: a.AspPubkey, - } - witnessSize = 64 * 2 - default: - return nil, fmt.Errorf("unsupported vtxo script: %T", vtxoScript) + forfeitClosures := vtxoScript.ForfeitClosures() + if len(forfeitClosures) <= 0 { + return nil, fmt.Errorf("no forfeit closures found") } - forfeitLeaf, err := forfeitClosure.Leaf() + forfeitClosure := forfeitClosures[0] + + forfeitScript, err := forfeitClosure.Script() if err != nil { return nil, err } + forfeitLeaf := txscript.NewBaseTapLeaf(forfeitScript) leafProof, err := vtxoTapTree.GetTaprootMerkleProof(forfeitLeaf.TapHash()) if err != nil { return nil, err @@ -2168,7 +2125,7 @@ func (a *covenantlessArkClient) createAndSignForfeits( RevealedScript: leafProof.Script, ControlBlock: ctrlBlock, }, - witnessSize, + forfeitClosure.WitnessSize(), parsedScript.Class(), ) if err != nil { @@ -2221,25 +2178,23 @@ func (a *covenantlessArkClient) coinSelectOnchain( fetchedUtxos := make([]types.Utxo, 0) for _, addr := range boardingAddrs { - boardingScript, err := bitcointree.ParseVtxoScript(addr.Descriptor) + boardingScript, err := bitcointree.ParseVtxoScript(addr.Tapscripts) if err != nil { return nil, 0, err } - var boardingTimeout uint - - if defaultVtxo, ok := boardingScript.(*bitcointree.DefaultVtxoScript); ok { - boardingTimeout = defaultVtxo.ExitDelay - } else { - return nil, 0, fmt.Errorf("unsupported boarding descriptor: %s", addr.Descriptor) + boardingTimeout, err := boardingScript.SmallestExitDelay() + if err != nil { + return nil, 0, err } + utxos, err := a.explorer.GetUtxos(addr.Address) if err != nil { return nil, 0, err } for _, utxo := range utxos { - u := utxo.ToUtxo(boardingTimeout, addr.Descriptor) + u := utxo.ToUtxo(boardingTimeout, addr.Tapscripts) if u.SpendableAt.Before(now) { fetchedUtxos = append(fetchedUtxos, u) } @@ -2275,7 +2230,7 @@ func (a *covenantlessArkClient) coinSelectOnchain( } for _, utxo := range utxos { - u := utxo.ToUtxo(uint(a.UnilateralExitDelay), addr.Descriptor) + u := utxo.ToUtxo(uint(a.UnilateralExitDelay), addr.Tapscripts) if u.SpendableAt.Before(now) { fetchedUtxos = append(fetchedUtxos, u) } @@ -2407,7 +2362,7 @@ func (a *covenantlessArkClient) getAllBoardingUtxos( VOut: uint32(i), Amount: vout.Amount, CreatedAt: createdAt, - Descriptor: addr.Descriptor, + Tapscripts: addr.Tapscripts, Spent: spent, }) } @@ -2427,17 +2382,14 @@ func (a *covenantlessArkClient) getClaimableBoardingUtxos(ctx context.Context, o claimable := make([]types.Utxo, 0) for _, addr := range boardingAddrs { - boardingScript, err := bitcointree.ParseVtxoScript(addr.Descriptor) + boardingScript, err := bitcointree.ParseVtxoScript(addr.Tapscripts) if err != nil { return nil, err } - var boardingTimeout uint - - if defaultVtxo, ok := boardingScript.(*bitcointree.DefaultVtxoScript); ok { - boardingTimeout = defaultVtxo.ExitDelay - } else { - return nil, fmt.Errorf("unsupported boarding descriptor: %s", addr.Descriptor) + boardingTimeout, err := boardingScript.SmallestExitDelay() + if err != nil { + return nil, err } boardingUtxos, err := a.explorer.GetUtxos(addr.Address) @@ -2466,7 +2418,7 @@ func (a *covenantlessArkClient) getClaimableBoardingUtxos(ctx context.Context, o } } - u := utxo.ToUtxo(boardingTimeout, addr.Descriptor) + u := utxo.ToUtxo(boardingTimeout, addr.Tapscripts) if u.SpendableAt.Before(now) { continue } diff --git a/pkg/client-sdk/explorer/explorer.go b/pkg/client-sdk/explorer/explorer.go index 3c64e63..aeb5270 100644 --- a/pkg/client-sdk/explorer/explorer.go +++ b/pkg/client-sdk/explorer/explorer.go @@ -53,8 +53,8 @@ type SpentStatus struct { SpentBy string `json:"txid,omitempty"` } -func (e ExplorerUtxo) ToUtxo(delay uint, descriptor string) types.Utxo { - return newUtxo(e, delay, descriptor) +func (e ExplorerUtxo) ToUtxo(delay uint, tapscripts []string) types.Utxo { + return newUtxo(e, delay, tapscripts) } type Explorer interface { @@ -415,7 +415,7 @@ func parseBitcoinTx(txStr string) (string, string, error) { return txhex, txid, nil } -func newUtxo(explorerUtxo ExplorerUtxo, delay uint, descriptor string) types.Utxo { +func newUtxo(explorerUtxo ExplorerUtxo, delay uint, tapscripts []string) types.Utxo { utxoTime := explorerUtxo.Status.Blocktime createdAt := time.Unix(utxoTime, 0) if utxoTime == 0 { @@ -431,6 +431,6 @@ func newUtxo(explorerUtxo ExplorerUtxo, delay uint, descriptor string) types.Utx Delay: delay, SpendableAt: time.Unix(utxoTime, 0).Add(time.Duration(delay) * time.Second), CreatedAt: createdAt, - Descriptor: descriptor, + Tapscripts: tapscripts, } } diff --git a/pkg/client-sdk/internal/utils/utils.go b/pkg/client-sdk/internal/utils/utils.go index eaeabbe..40fb3a7 100644 --- a/pkg/client-sdk/internal/utils/utils.go +++ b/pkg/client-sdk/internal/utils/utils.go @@ -24,12 +24,12 @@ import ( func CoinSelect( boardingUtxos []types.Utxo, - vtxos []client.DescriptorVtxo, + vtxos []client.TapscriptsVtxo, amount, dust uint64, sortByExpirationTime bool, -) ([]types.Utxo, []client.DescriptorVtxo, uint64, error) { - selected, notSelected := make([]client.DescriptorVtxo, 0), make([]client.DescriptorVtxo, 0) +) ([]types.Utxo, []client.TapscriptsVtxo, uint64, error) { + selected, notSelected := make([]client.TapscriptsVtxo, 0), make([]client.TapscriptsVtxo, 0) selectedBoarding, notSelectedBoarding := make([]types.Utxo, 0), make([]types.Utxo, 0) selectedAmount := uint64(0) diff --git a/pkg/client-sdk/redemption/covenantless_redeem.go b/pkg/client-sdk/redemption/covenantless_redeem.go index d1bf096..1b15830 100644 --- a/pkg/client-sdk/redemption/covenantless_redeem.go +++ b/pkg/client-sdk/redemption/covenantless_redeem.go @@ -7,7 +7,6 @@ import ( "strings" "time" - "github.com/ark-network/ark/common/bitcointree" "github.com/ark-network/ark/common/tree" "github.com/ark-network/ark/pkg/client-sdk/client" "github.com/ark-network/ark/pkg/client-sdk/explorer" @@ -172,7 +171,7 @@ func findCovenantlessSweepClosure( var seconds uint var sweepClosure *txscript.TapLeaf for _, tapLeaf := range tx.Inputs[0].TaprootLeafScript { - closure := &bitcointree.CSVSigClosure{} + closure := &tree.CSVSigClosure{} valid, err := closure.Decode(tapLeaf.Script) if err != nil { continue diff --git a/pkg/client-sdk/types/types.go b/pkg/client-sdk/types/types.go index b034c05..cdb4e70 100644 --- a/pkg/client-sdk/types/types.go +++ b/pkg/client-sdk/types/types.go @@ -113,7 +113,7 @@ type Utxo struct { Delay uint SpendableAt time.Time CreatedAt time.Time - Descriptor string + Tapscripts []string Spent bool } diff --git a/pkg/client-sdk/wallet/singlekey/bitcoin_wallet.go b/pkg/client-sdk/wallet/singlekey/bitcoin_wallet.go index 94f1326..70cf402 100644 --- a/pkg/client-sdk/wallet/singlekey/bitcoin_wallet.go +++ b/pkg/client-sdk/wallet/singlekey/bitcoin_wallet.go @@ -9,6 +9,7 @@ import ( "github.com/ark-network/ark/common" "github.com/ark-network/ark/common/bitcointree" + "github.com/ark-network/ark/common/tree" "github.com/ark-network/ark/pkg/client-sdk/explorer" "github.com/ark-network/ark/pkg/client-sdk/internal/utils" "github.com/ark-network/ark/pkg/client-sdk/types" @@ -43,7 +44,7 @@ func NewBitcoinWallet( func (w *bitcoinWallet) GetAddresses( ctx context.Context, -) ([]wallet.DescriptorAddress, []wallet.DescriptorAddress, []wallet.DescriptorAddress, error) { +) ([]wallet.TapscriptsAddress, []wallet.TapscriptsAddress, []wallet.TapscriptsAddress, error) { offchainAddr, boardingAddr, err := w.getAddress(ctx) if err != nil { return nil, nil, nil, err @@ -69,21 +70,21 @@ func (w *bitcoinWallet) GetAddresses( return nil, nil, nil, err } - offchainAddrs := []wallet.DescriptorAddress{ + offchainAddrs := []wallet.TapscriptsAddress{ { - Descriptor: offchainAddr.Descriptor, + Tapscripts: offchainAddr.Tapscripts, Address: encodedOffchainAddr, }, } - boardingAddrs := []wallet.DescriptorAddress{ + boardingAddrs := []wallet.TapscriptsAddress{ { - Descriptor: boardingAddr.Descriptor, + Tapscripts: boardingAddr.Tapscripts, Address: boardingAddr.Address, }, } - redemptionAddrs := []wallet.DescriptorAddress{ + redemptionAddrs := []wallet.TapscriptsAddress{ { - Descriptor: offchainAddr.Descriptor, + Tapscripts: offchainAddr.Tapscripts, Address: redemptionAddr.EncodeAddress(), }, } @@ -92,7 +93,7 @@ func (w *bitcoinWallet) GetAddresses( func (w *bitcoinWallet) NewAddress( ctx context.Context, _ bool, -) (*wallet.DescriptorAddress, *wallet.DescriptorAddress, error) { +) (*wallet.TapscriptsAddress, *wallet.TapscriptsAddress, error) { offchainAddr, boardingAddr, err := w.getAddress(ctx) if err != nil { return nil, nil, err @@ -103,34 +104,34 @@ func (w *bitcoinWallet) NewAddress( return nil, nil, err } - return &wallet.DescriptorAddress{ - Descriptor: offchainAddr.Descriptor, + return &wallet.TapscriptsAddress{ + Tapscripts: offchainAddr.Tapscripts, Address: encodedOffchainAddr, }, boardingAddr, nil } func (w *bitcoinWallet) NewAddresses( ctx context.Context, _ bool, num int, -) ([]wallet.DescriptorAddress, []wallet.DescriptorAddress, error) { +) ([]wallet.TapscriptsAddress, []wallet.TapscriptsAddress, error) { offchainAddr, boardingAddr, err := w.getAddress(ctx) if err != nil { return nil, nil, err } - offchainAddrs := make([]wallet.DescriptorAddress, 0, num) - boardingAddrs := make([]wallet.DescriptorAddress, 0, num) + offchainAddrs := make([]wallet.TapscriptsAddress, 0, num) + boardingAddrs := make([]wallet.TapscriptsAddress, 0, num) for i := 0; i < num; i++ { encodedOffchainAddr, err := offchainAddr.Address.Encode() if err != nil { return nil, nil, err } - offchainAddrs = append(offchainAddrs, wallet.DescriptorAddress{ - Descriptor: offchainAddr.Descriptor, + offchainAddrs = append(offchainAddrs, wallet.TapscriptsAddress{ + Tapscripts: offchainAddr.Tapscripts, Address: encodedOffchainAddr, }) - boardingAddrs = append(boardingAddrs, wallet.DescriptorAddress{ - Descriptor: boardingAddr.Descriptor, + boardingAddrs = append(boardingAddrs, wallet.TapscriptsAddress{ + Tapscripts: boardingAddr.Tapscripts, Address: boardingAddr.Address, }) } @@ -192,12 +193,12 @@ func (s *bitcoinWallet) SignTransaction( ) txsighashes := txscript.NewTxSigHashes(updater.Upsbt.UnsignedTx, prevoutFetcher) + myPubkey := schnorr.SerializePubKey(s.walletData.Pubkey) for i, input := range ptx.Inputs { if len(input.TaprootLeafScript) > 0 { - pubkey := s.walletData.Pubkey for _, leaf := range input.TaprootLeafScript { - closure, err := bitcointree.DecodeClosure(leaf.Script) + closure, err := tree.DecodeClosure(leaf.Script) if err != nil { return "", err } @@ -205,10 +206,20 @@ func (s *bitcoinWallet) SignTransaction( sign := false switch c := closure.(type) { - case *bitcointree.CSVSigClosure: - sign = bytes.Equal(c.Pubkey.SerializeCompressed()[1:], pubkey.SerializeCompressed()[1:]) - case *bitcointree.MultisigClosure: - sign = bytes.Equal(c.Pubkey.SerializeCompressed()[1:], pubkey.SerializeCompressed()[1:]) + case *tree.CSVSigClosure: + for _, key := range c.MultisigClosure.PubKeys { + if bytes.Equal(schnorr.SerializePubKey(key), myPubkey) { + sign = true + break + } + } + case *tree.MultisigClosure: + for _, key := range c.PubKeys { + if bytes.Equal(schnorr.SerializePubKey(key), myPubkey) { + sign = true + break + } + } } if sign { @@ -235,7 +246,7 @@ func (s *bitcoinWallet) SignTransaction( return "", err } - if !sig.Verify(preimage, pubkey) { + if !sig.Verify(preimage, s.walletData.Pubkey) { return "", fmt.Errorf("signature verification failed") } @@ -244,7 +255,7 @@ func (s *bitcoinWallet) SignTransaction( } updater.Upsbt.Inputs[i].TaprootScriptSpendSig = append(updater.Upsbt.Inputs[i].TaprootScriptSpendSig, &psbt.TaprootScriptSpendSig{ - XOnlyPubKey: schnorr.SerializePubKey(pubkey), + XOnlyPubKey: myPubkey, LeafHash: hash.CloneBytes(), Signature: sig.Serialize(), SigHash: txscript.SigHashDefault, @@ -259,17 +270,12 @@ func (s *bitcoinWallet) SignTransaction( } func (w *bitcoinWallet) SignMessage( - ctx context.Context, message []byte, pubkey string, + ctx context.Context, message []byte, ) (string, error) { if w.IsLocked() { return "", fmt.Errorf("wallet is locked") } - walletPubkeyHex := hex.EncodeToString(schnorr.SerializePubKey(w.walletData.Pubkey)) - if walletPubkeyHex != pubkey { - return "", fmt.Errorf("pubkey mismatch, cannot sign message") - } - sig, err := schnorr.Sign(w.privateKey, message) if err != nil { return "", err @@ -278,14 +284,16 @@ func (w *bitcoinWallet) SignMessage( return hex.EncodeToString(sig.Serialize()), nil } +type addressWithTapscripts struct { + Address common.Address + Tapscripts []string +} + func (w *bitcoinWallet) getAddress( ctx context.Context, ) ( - *struct { - Address common.Address - Descriptor string - }, - *wallet.DescriptorAddress, + *addressWithTapscripts, + *wallet.TapscriptsAddress, error, ) { if w.walletData == nil { @@ -299,11 +307,11 @@ func (w *bitcoinWallet) getAddress( netParams := utils.ToBitcoinNetwork(data.Network) - defaultVtxoScript := &bitcointree.DefaultVtxoScript{ - Asp: data.AspPubkey, - Owner: w.walletData.Pubkey, - ExitDelay: uint(data.UnilateralExitDelay), - } + defaultVtxoScript := bitcointree.NewDefaultVtxoScript( + w.walletData.Pubkey, + data.AspPubkey, + uint(data.UnilateralExitDelay), + ) vtxoTapKey, _, err := defaultVtxoScript.TapTree() if err != nil { @@ -316,16 +324,12 @@ func (w *bitcoinWallet) getAddress( VtxoTapKey: vtxoTapKey, } - myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(w.walletData.Pubkey)) - descriptorStr := strings.ReplaceAll( - data.BoardingDescriptorTemplate, "USER", myPubkeyStr, + boardingVtxoScript := bitcointree.NewDefaultVtxoScript( + w.walletData.Pubkey, + data.AspPubkey, + uint(data.UnilateralExitDelay*2), ) - boardingVtxoScript, err := bitcointree.ParseVtxoScript(descriptorStr) - if err != nil { - return nil, nil, err - } - boardingTapKey, _, err := boardingVtxoScript.TapTree() if err != nil { return nil, nil, err @@ -339,14 +343,22 @@ func (w *bitcoinWallet) getAddress( return nil, nil, err } - return &struct { - Address common.Address - Descriptor string - }{ - *offchainAddress, defaultVtxoScript.ToDescriptor(), + tapscripts, err := defaultVtxoScript.Encode() + if err != nil { + return nil, nil, err + } + + boardingTapscripts, err := boardingVtxoScript.Encode() + if err != nil { + return nil, nil, err + } + + return &addressWithTapscripts{ + Address: *offchainAddress, + Tapscripts: tapscripts, }, - &wallet.DescriptorAddress{ - Descriptor: descriptorStr, + &wallet.TapscriptsAddress{ + Tapscripts: boardingTapscripts, Address: boardingAddr.EncodeAddress(), }, nil diff --git a/pkg/client-sdk/wallet/singlekey/liquid_wallet.go b/pkg/client-sdk/wallet/singlekey/liquid_wallet.go index fd26307..c89dd62 100644 --- a/pkg/client-sdk/wallet/singlekey/liquid_wallet.go +++ b/pkg/client-sdk/wallet/singlekey/liquid_wallet.go @@ -5,7 +5,6 @@ import ( "context" "encoding/hex" "fmt" - "strings" "github.com/ark-network/ark/common" "github.com/ark-network/ark/common/tree" @@ -44,7 +43,7 @@ func NewLiquidWallet( func (w *liquidWallet) GetAddresses( ctx context.Context, -) ([]wallet.DescriptorAddress, []wallet.DescriptorAddress, []wallet.DescriptorAddress, error) { +) ([]wallet.TapscriptsAddress, []wallet.TapscriptsAddress, []wallet.TapscriptsAddress, error) { offchainAddr, boardingAddr, err := w.getAddress(ctx) if err != nil { return nil, nil, nil, err @@ -72,22 +71,22 @@ func (w *liquidWallet) GetAddresses( return nil, nil, nil, err } - offchainAddrs := []wallet.DescriptorAddress{ + offchainAddrs := []wallet.TapscriptsAddress{ { - Descriptor: offchainAddr.Descriptor, + Tapscripts: offchainAddr.Tapscripts, Address: encodedOffchainAddr, }, } - boardingAddrs := []wallet.DescriptorAddress{ + boardingAddrs := []wallet.TapscriptsAddress{ { - Descriptor: boardingAddr.Descriptor, + Tapscripts: boardingAddr.Tapscripts, Address: boardingAddr.Address, }, } - redemptionAddrs := []wallet.DescriptorAddress{ + redemptionAddrs := []wallet.TapscriptsAddress{ { - Descriptor: offchainAddr.Descriptor, + Tapscripts: offchainAddr.Tapscripts, Address: redemptionAddr, }, } @@ -97,7 +96,7 @@ func (w *liquidWallet) GetAddresses( func (w *liquidWallet) NewAddress( ctx context.Context, _ bool, -) (*wallet.DescriptorAddress, *wallet.DescriptorAddress, error) { +) (*wallet.TapscriptsAddress, *wallet.TapscriptsAddress, error) { offchainAddr, boardingAddr, err := w.getAddress(ctx) if err != nil { return nil, nil, err @@ -108,37 +107,37 @@ func (w *liquidWallet) NewAddress( return nil, nil, err } - return &wallet.DescriptorAddress{ - Descriptor: offchainAddr.Descriptor, + return &wallet.TapscriptsAddress{ + Tapscripts: offchainAddr.Tapscripts, Address: encodedOffchainAddr, - }, &wallet.DescriptorAddress{ - Descriptor: boardingAddr.Descriptor, + }, &wallet.TapscriptsAddress{ + Tapscripts: boardingAddr.Tapscripts, Address: boardingAddr.Address, }, nil } func (w *liquidWallet) NewAddresses( ctx context.Context, _ bool, num int, -) ([]wallet.DescriptorAddress, []wallet.DescriptorAddress, error) { +) ([]wallet.TapscriptsAddress, []wallet.TapscriptsAddress, error) { offchainAddr, boardingAddr, err := w.getAddress(ctx) if err != nil { return nil, nil, err } - offchainAddrs := make([]wallet.DescriptorAddress, 0, num) - boardingAddrs := make([]wallet.DescriptorAddress, 0, num) + offchainAddrs := make([]wallet.TapscriptsAddress, 0, num) + boardingAddrs := make([]wallet.TapscriptsAddress, 0, num) for i := 0; i < num; i++ { encodedOffchainAddr, err := offchainAddr.Address.Encode() if err != nil { return nil, nil, err } - offchainAddrs = append(offchainAddrs, wallet.DescriptorAddress{ - Descriptor: offchainAddr.Descriptor, + offchainAddrs = append(offchainAddrs, wallet.TapscriptsAddress{ + Tapscripts: offchainAddr.Tapscripts, Address: encodedOffchainAddr, }) - boardingAddrs = append(boardingAddrs, wallet.DescriptorAddress{ - Descriptor: boardingAddr.Descriptor, + boardingAddrs = append(boardingAddrs, wallet.TapscriptsAddress{ + Tapscripts: boardingAddr.Tapscripts, Address: boardingAddr.Address, }) } @@ -212,7 +211,7 @@ func (s *liquidWallet) SignTransaction( prevoutsAssets = append(prevoutsAssets, input.WitnessUtxo.Asset) } - serializedPubKey := s.walletData.Pubkey.SerializeCompressed() + myPubkey := schnorr.SerializePubKey(s.walletData.Pubkey) for i, input := range pset.Inputs { if len(input.TapLeafScript) > 0 { @@ -230,9 +229,19 @@ func (s *liquidWallet) SignTransaction( sign := false switch c := closure.(type) { case *tree.CSVSigClosure: - sign = bytes.Equal(c.Pubkey.SerializeCompressed()[1:], serializedPubKey[1:]) + for _, key := range c.MultisigClosure.PubKeys { + if bytes.Equal(schnorr.SerializePubKey(key), myPubkey) { + sign = true + break + } + } case *tree.MultisigClosure: - sign = bytes.Equal(c.Pubkey.SerializeCompressed()[1:], serializedPubKey[1:]) + for _, key := range c.PubKeys { + if bytes.Equal(schnorr.SerializePubKey(key), myPubkey) { + sign = true + break + } + } } if sign { @@ -288,17 +297,12 @@ func (s *liquidWallet) SignTransaction( } func (w *liquidWallet) SignMessage( - ctx context.Context, message []byte, pubkey string, + ctx context.Context, message []byte, ) (string, error) { if w.IsLocked() { return "", fmt.Errorf("wallet is locked") } - walletPubkeyHex := hex.EncodeToString(schnorr.SerializePubKey(w.walletData.Pubkey)) - if walletPubkeyHex != pubkey { - return "", fmt.Errorf("pubkey mismatch, cannot sign message") - } - sig, err := schnorr.Sign(w.privateKey, message) if err != nil { return "", err @@ -310,11 +314,8 @@ func (w *liquidWallet) SignMessage( func (w *liquidWallet) getAddress( ctx context.Context, ) ( - *struct { - Address common.Address - Descriptor string - }, - *wallet.DescriptorAddress, + *addressWithTapscripts, + *wallet.TapscriptsAddress, error, ) { if w.walletData == nil { @@ -328,11 +329,11 @@ func (w *liquidWallet) getAddress( liquidNet := utils.ToElementsNetwork(data.Network) - vtxoScript := &tree.DefaultVtxoScript{ - Owner: w.walletData.Pubkey, - Asp: data.AspPubkey, - ExitDelay: uint(data.UnilateralExitDelay), - } + vtxoScript := tree.NewDefaultVtxoScript( + w.walletData.Pubkey, + data.AspPubkey, + uint(data.UnilateralExitDelay), + ) vtxoTapKey, _, err := vtxoScript.TapTree() if err != nil { @@ -345,22 +346,18 @@ func (w *liquidWallet) getAddress( VtxoTapKey: vtxoTapKey, } - myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(w.walletData.Pubkey)) - descriptorStr := strings.ReplaceAll( - data.BoardingDescriptorTemplate, "USER", myPubkeyStr, + boardingVtxoScript := tree.NewDefaultVtxoScript( + w.walletData.Pubkey, + data.AspPubkey, + uint(data.UnilateralExitDelay*2), ) - onboardingScript, err := tree.ParseVtxoScript(descriptorStr) + boardingTapKey, _, err := boardingVtxoScript.TapTree() if err != nil { return nil, nil, err } - tapKey, _, err := onboardingScript.TapTree() - if err != nil { - return nil, nil, err - } - - p2tr, err := payment.FromTweakedKey(tapKey, &liquidNet, nil) + p2tr, err := payment.FromTweakedKey(boardingTapKey, &liquidNet, nil) if err != nil { return nil, nil, err } @@ -370,14 +367,21 @@ func (w *liquidWallet) getAddress( return nil, nil, err } - return &struct { - Address common.Address - Descriptor string - }{ + tapscripts, err := vtxoScript.Encode() + if err != nil { + return nil, nil, err + } + + boardingTapscripts, err := boardingVtxoScript.Encode() + if err != nil { + return nil, nil, err + } + + return &addressWithTapscripts{ Address: *offchainAddr, - Descriptor: vtxoScript.ToDescriptor(), - }, &wallet.DescriptorAddress{ - Descriptor: descriptorStr, + Tapscripts: tapscripts, + }, &wallet.TapscriptsAddress{ + Tapscripts: boardingTapscripts, Address: boardingAddr, }, nil } diff --git a/pkg/client-sdk/wallet/wallet.go b/pkg/client-sdk/wallet/wallet.go index 284eca6..f50772a 100644 --- a/pkg/client-sdk/wallet/wallet.go +++ b/pkg/client-sdk/wallet/wallet.go @@ -10,8 +10,8 @@ const ( SingleKeyWallet = "singlekey" ) -type DescriptorAddress struct { - Descriptor string +type TapscriptsAddress struct { + Tapscripts []string Address string } @@ -25,18 +25,18 @@ type WalletService interface { IsLocked() bool GetAddresses( ctx context.Context, - ) (offchainAddresses, boardingAddresses, redemptionAddresses []DescriptorAddress, err error) + ) (offchainAddresses, boardingAddresses, redemptionAddresses []TapscriptsAddress, err error) NewAddress( ctx context.Context, change bool, - ) (offchainAddr, onchainAddr *DescriptorAddress, err error) + ) (offchainAddr, onchainAddr *TapscriptsAddress, err error) NewAddresses( ctx context.Context, change bool, num int, - ) (offchainAddresses, onchainAddresses []DescriptorAddress, err error) + ) (offchainAddresses, onchainAddresses []TapscriptsAddress, err error) SignTransaction( ctx context.Context, explorerSvc explorer.Explorer, tx string, ) (signedTx string, err error) SignMessage( - ctx context.Context, message []byte, pubkey string, + ctx context.Context, message []byte, ) (signature string, err error) Dump(ctx context.Context) (seed string, err error) } diff --git a/server/internal/core/application/covenant.go b/server/internal/core/application/covenant.go index a1c6ade..374ac19 100644 --- a/server/internal/core/application/covenant.go +++ b/server/internal/core/application/covenant.go @@ -9,7 +9,6 @@ import ( "time" "github.com/ark-network/ark/common" - "github.com/ark-network/ark/common/descriptor" "github.com/ark-network/ark/common/note" "github.com/ark-network/ark/common/tree" "github.com/ark-network/ark/server/internal/core/domain" @@ -149,29 +148,30 @@ func (s *covenantService) Stop() { close(s.eventsCh) } -func (s *covenantService) GetBoardingAddress(ctx context.Context, userPubkey *secp256k1.PublicKey) (string, string, error) { - vtxoScript := &tree.DefaultVtxoScript{ - Asp: s.pubkey, - Owner: userPubkey, - ExitDelay: uint(s.boardingExitDelay), - } +func (s *covenantService) GetBoardingAddress(ctx context.Context, userPubkey *secp256k1.PublicKey) (string, []string, error) { + vtxoScript := tree.NewDefaultVtxoScript(userPubkey, s.pubkey, uint(s.boardingExitDelay)) tapKey, _, err := vtxoScript.TapTree() if err != nil { - return "", "", fmt.Errorf("failed to get taproot key: %s", err) + return "", nil, fmt.Errorf("failed to get taproot key: %s", err) } p2tr, err := payment.FromTweakedKey(tapKey, s.onchainNetwork(), nil) if err != nil { - return "", "", err + return "", nil, err } addr, err := p2tr.TaprootAddress() if err != nil { - return "", "", err + return "", nil, err } - return addr, vtxoScript.ToDescriptor(), nil + scripts, err := vtxoScript.Encode() + if err != nil { + return "", nil, err + } + + return addr, scripts, nil } func (s *covenantService) SpendNotes(_ context.Context, _ []note.Note) (string, error) { @@ -211,8 +211,18 @@ func (s *covenantService) SpendVtxos(ctx context.Context, inputs []ports.Input) return "", fmt.Errorf("tx %s not confirmed", input.Txid) } + vtxoScript, err := tree.ParseVtxoScript(input.Tapscripts) + if err != nil { + return "", fmt.Errorf("failed to parse boarding descriptor: %s", err) + } + + exitDelay, err := vtxoScript.SmallestExitDelay() + if err != nil { + return "", fmt.Errorf("failed to get exit delay: %s", err) + } + // if the exit path is available, forbid registering the boarding utxo - if blocktime+int64(s.boardingExitDelay) < now { + if blocktime+int64(exitDelay) < now { return "", fmt.Errorf("tx %s expired", input.Txid) } @@ -242,7 +252,7 @@ func (s *covenantService) SpendVtxos(ctx context.Context, inputs []ports.Input) return "", fmt.Errorf("input %s:%d already swept", vtxo.Txid, vtxo.VOut) } - vtxoScript, err := tree.ParseVtxoScript(input.Descriptor) + vtxoScript, err := tree.ParseVtxoScript(input.Tapscripts) if err != nil { return "", fmt.Errorf("failed to parse boarding descriptor: %s", err) } @@ -290,7 +300,7 @@ func (s *covenantService) newBoardingInput(tx *transaction.Transaction, input po return nil, fmt.Errorf("failed to parse value: %s", err) } - boardingScript, err := tree.ParseVtxoScript(input.Descriptor) + boardingScript, err := tree.ParseVtxoScript(input.Tapscripts) if err != nil { return nil, fmt.Errorf("failed to parse boarding descriptor: %s", err) } @@ -309,16 +319,8 @@ func (s *covenantService) newBoardingInput(tx *transaction.Transaction, input po return nil, fmt.Errorf("descriptor does not match script in transaction output") } - if defaultVtxoScript, ok := boardingScript.(*tree.DefaultVtxoScript); ok { - if !bytes.Equal(schnorr.SerializePubKey(defaultVtxoScript.Asp), schnorr.SerializePubKey(s.pubkey)) { - return nil, fmt.Errorf("invalid boarding descriptor, ASP mismatch") - } - - if defaultVtxoScript.ExitDelay != uint(s.boardingExitDelay) { - return nil, fmt.Errorf("invalid boarding descriptor, timeout mismatch") - } - } else { - return nil, fmt.Errorf("only default vtxo script is supported for boarding") + if err := boardingScript.Validate(s.pubkey, uint(s.unilateralExitDelay)); err != nil { + return nil, err } return &ports.BoardingInput{ @@ -430,15 +432,7 @@ func (s *covenantService) GetInfo(ctx context.Context) (*ServiceInfo, error) { RoundInterval: s.roundInterval, Network: s.network.Name, Dust: dust, - BoardingDescriptorTemplate: fmt.Sprintf( - descriptor.DefaultVtxoDescriptorTemplate, - hex.EncodeToString(tree.UnspendableKey().SerializeCompressed()), - "USER", - hex.EncodeToString(schnorr.SerializePubKey(s.pubkey)), - s.boardingExitDelay, - "USER", - ), - ForfeitAddress: forfeitAddress, + ForfeitAddress: forfeitAddress, }, nil } @@ -852,6 +846,7 @@ func (s *covenantService) reactToFraud(ctx context.Context, vtxo domain.Vtxo, mu forfeitTxid, err := s.wallet.BroadcastTransaction(ctx, forfeitTxHex) if err != nil { + log.Debug(forfeitTxHex) return fmt.Errorf("failed to broadcast forfeit tx: %s", err) } diff --git a/server/internal/core/application/covenantless.go b/server/internal/core/application/covenantless.go index f7e34db..470272a 100644 --- a/server/internal/core/application/covenantless.go +++ b/server/internal/core/application/covenantless.go @@ -11,7 +11,6 @@ import ( "github.com/ark-network/ark/common" "github.com/ark-network/ark/common/bitcointree" - "github.com/ark-network/ark/common/descriptor" "github.com/ark-network/ark/common/note" "github.com/ark-network/ark/common/tree" "github.com/ark-network/ark/server/internal/core/domain" @@ -259,7 +258,7 @@ func (s *covenantlessService) CompleteAsyncPayment( } // verify the tapscript signatures - if valid, _, err := s.builder.VerifyTapscriptPartialSigs(tx); err != nil || !valid { + if valid, err := s.builder.VerifyTapscriptPartialSigs(tx); err != nil || !valid { return fmt.Errorf("invalid tx signature: %s", err) } } @@ -329,12 +328,12 @@ func (s *covenantlessService) CreateAsyncPayment( ctx context.Context, inputs []AsyncPaymentInput, receivers []domain.Receiver, ) (string, error) { vtxosKeys := make([]domain.VtxoKey, 0, len(inputs)) - descriptors := make(map[domain.VtxoKey]string) + scripts := make(map[domain.VtxoKey][]string) forfeitLeaves := make(map[domain.VtxoKey]chainhash.Hash) for _, in := range inputs { vtxosKeys = append(vtxosKeys, in.VtxoKey) - descriptors[in.VtxoKey] = in.Descriptor + scripts[in.VtxoKey] = in.Tapscripts forfeitLeaves[in.VtxoKey] = in.ForfeitLeafHash } @@ -373,7 +372,7 @@ func (s *covenantlessService) CreateAsyncPayment( } redeemTx, err := s.builder.BuildAsyncPaymentTransactions( - vtxosInputs, descriptors, forfeitLeaves, receivers, + vtxosInputs, scripts, forfeitLeaves, receivers, ) if err != nil { return "", fmt.Errorf("failed to build async payment txs: %s", err) @@ -395,26 +394,29 @@ func (s *covenantlessService) CreateAsyncPayment( func (s *covenantlessService) GetBoardingAddress( ctx context.Context, userPubkey *secp256k1.PublicKey, -) (address string, descriptor string, err error) { - vtxoScript := &bitcointree.DefaultVtxoScript{ - Asp: s.pubkey, - Owner: userPubkey, - ExitDelay: uint(s.boardingExitDelay), - } +) (address string, scripts []string, err error) { + vtxoScript := bitcointree.NewDefaultVtxoScript(s.pubkey, userPubkey, uint(s.boardingExitDelay)) tapKey, _, err := vtxoScript.TapTree() if err != nil { - return "", "", fmt.Errorf("failed to get taproot key: %s", err) + return "", nil, fmt.Errorf("failed to get taproot key: %s", err) } addr, err := btcutil.NewAddressTaproot( schnorr.SerializePubKey(tapKey), s.chainParams(), ) if err != nil { - return "", "", fmt.Errorf("failed to get address: %s", err) + return "", nil, fmt.Errorf("failed to get address: %s", err) } - return addr.EncodeAddress(), vtxoScript.ToDescriptor(), nil + scripts, err = vtxoScript.Encode() + if err != nil { + return "", nil, fmt.Errorf("failed to encode vtxo script: %s", err) + } + + address = addr.EncodeAddress() + + return } func (s *covenantlessService) SpendNotes(ctx context.Context, notes []note.Note) (string, error) { @@ -489,8 +491,18 @@ func (s *covenantlessService) SpendVtxos(ctx context.Context, inputs []ports.Inp return "", fmt.Errorf("tx %s not confirmed", input.Txid) } + vtxoScript, err := bitcointree.ParseVtxoScript(input.Tapscripts) + if err != nil { + return "", fmt.Errorf("failed to parse boarding descriptor: %s", err) + } + + exitDelay, err := vtxoScript.SmallestExitDelay() + if err != nil { + return "", fmt.Errorf("failed to get exit delay: %s", err) + } + // if the exit path is available, forbid registering the boarding utxo - if blocktime+int64(s.boardingExitDelay) < now { + if blocktime+int64(exitDelay) < now { return "", fmt.Errorf("tx %s expired", input.Txid) } @@ -520,7 +532,7 @@ func (s *covenantlessService) SpendVtxos(ctx context.Context, inputs []ports.Inp return "", fmt.Errorf("input %s:%d already swept", vtxo.Txid, vtxo.VOut) } - vtxoScript, err := bitcointree.ParseVtxoScript(input.Descriptor) + vtxoScript, err := bitcointree.ParseVtxoScript(input.Tapscripts) if err != nil { return "", fmt.Errorf("failed to parse boarding descriptor: %s", err) } @@ -559,7 +571,7 @@ func (s *covenantlessService) newBoardingInput(tx wire.MsgTx, input ports.Input) output := tx.TxOut[input.VtxoKey.VOut] - boardingScript, err := bitcointree.ParseVtxoScript(input.Descriptor) + boardingScript, err := bitcointree.ParseVtxoScript(input.Tapscripts) if err != nil { return nil, fmt.Errorf("failed to parse boarding descriptor: %s", err) } @@ -578,16 +590,8 @@ func (s *covenantlessService) newBoardingInput(tx wire.MsgTx, input ports.Input) return nil, fmt.Errorf("descriptor does not match script in transaction output") } - if defaultVtxoScript, ok := boardingScript.(*bitcointree.DefaultVtxoScript); ok { - if !bytes.Equal(schnorr.SerializePubKey(defaultVtxoScript.Asp), schnorr.SerializePubKey(s.pubkey)) { - return nil, fmt.Errorf("invalid boarding descriptor, ASP mismatch") - } - - if defaultVtxoScript.ExitDelay != uint(s.boardingExitDelay) { - return nil, fmt.Errorf("invalid boarding descriptor, timeout mismatch") - } - } else { - return nil, fmt.Errorf("only default vtxo script is supported for boarding") + if err := boardingScript.Validate(s.pubkey, uint(s.unilateralExitDelay)); err != nil { + return nil, err } return &ports.BoardingInput{ @@ -691,15 +695,7 @@ func (s *covenantlessService) GetInfo(ctx context.Context) (*ServiceInfo, error) RoundInterval: s.roundInterval, Network: s.network.Name, Dust: dust, - BoardingDescriptorTemplate: fmt.Sprintf( - descriptor.DefaultVtxoDescriptorTemplate, - hex.EncodeToString(bitcointree.UnspendableKey().SerializeCompressed()), - "USER", - hex.EncodeToString(schnorr.SerializePubKey(s.pubkey)), - s.boardingExitDelay, - "USER", - ), - ForfeitAddress: forfeitAddr, + ForfeitAddress: forfeitAddr, }, nil } @@ -921,7 +917,7 @@ func (s *covenantlessService) startFinalization() { cosigners = append(cosigners, ephemeralKey.PubKey()) - unsignedRoundTx, tree, connectorAddress, connectors, err := s.builder.BuildRoundTx( + unsignedRoundTx, vtxoTree, connectorAddress, connectors, err := s.builder.BuildRoundTx( s.pubkey, payments, boardingInputs, @@ -937,7 +933,7 @@ func (s *covenantlessService) startFinalization() { s.forfeitTxs.init(connectors, payments) - if len(tree) > 0 { + if len(vtxoTree) > 0 { log.Debugf("signing congestion tree for round %s", round.Id) signingSession := newMusigSigningSession(len(cosigners)) @@ -947,14 +943,14 @@ func (s *covenantlessService) startFinalization() { s.currentRound.UnsignedTx = unsignedRoundTx // send back the unsigned tree & all cosigners pubkeys - s.propagateRoundSigningStartedEvent(tree, cosigners) + s.propagateRoundSigningStartedEvent(vtxoTree, cosigners) - sweepClosure := bitcointree.CSVSigClosure{ - Pubkey: s.pubkey, - Seconds: uint(s.roundLifetime), + sweepClosure := tree.CSVSigClosure{ + MultisigClosure: tree.MultisigClosure{PubKeys: []*secp256k1.PublicKey{s.pubkey}}, + Seconds: uint(s.roundLifetime), } - sweepTapLeaf, err := sweepClosure.Leaf() + sweepScript, err := sweepClosure.Script() if err != nil { return } @@ -968,10 +964,11 @@ func (s *covenantlessService) startFinalization() { sharedOutputAmount := unsignedPsbt.UnsignedTx.TxOut[0].Value - sweepTapTree := txscript.AssembleTaprootScriptTree(*sweepTapLeaf) + sweepLeaf := txscript.NewBaseTapLeaf(sweepScript) + sweepTapTree := txscript.AssembleTaprootScriptTree(sweepLeaf) root := sweepTapTree.RootNode.TapHash() - coordinator, err := bitcointree.NewTreeCoordinatorSession(sharedOutputAmount, tree, root.CloneBytes(), cosigners) + coordinator, err := bitcointree.NewTreeCoordinatorSession(sharedOutputAmount, vtxoTree, root.CloneBytes(), cosigners) if err != nil { round.Fail(fmt.Errorf("failed to create tree coordinator: %s", err)) log.WithError(err).Warn("failed to create tree coordinator") @@ -979,7 +976,7 @@ func (s *covenantlessService) startFinalization() { } aspSignerSession := bitcointree.NewTreeSignerSession( - ephemeralKey, sharedOutputAmount, tree, root.CloneBytes(), + ephemeralKey, sharedOutputAmount, vtxoTree, root.CloneBytes(), ) nonces, err := aspSignerSession.GetNonces() @@ -1085,11 +1082,11 @@ func (s *covenantlessService) startFinalization() { log.Debugf("congestion tree signed for round %s", round.Id) - tree = signedTree + vtxoTree = signedTree } if _, err := round.StartFinalization( - connectorAddress, connectors, tree, unsignedRoundTx, + connectorAddress, connectors, vtxoTree, unsignedRoundTx, ); err != nil { round.Fail(fmt.Errorf("failed to start finalization: %s", err)) log.WithError(err).Warn("failed to start finalization") diff --git a/server/internal/core/application/proof.go b/server/internal/core/application/proof.go index f497ab9..befc4af 100644 --- a/server/internal/core/application/proof.go +++ b/server/internal/core/application/proof.go @@ -24,7 +24,7 @@ type OwnershipProof struct { func (p OwnershipProof) validate(vtxo domain.Vtxo) error { // verify revealed script and extract user public key - pubkey, err := decodeForfeitClosure(p.Script) + pubkeys, err := decodeForfeitClosure(p.Script) if err != nil { return err } @@ -49,24 +49,32 @@ func (p OwnershipProof) validate(vtxo domain.Vtxo) error { outpointBytes := append(txhash[:], voutBytes...) sigMsg := sha256.Sum256(outpointBytes) - if !p.Signature.Verify(sigMsg[:], pubkey) { + valid := false + for _, pubkey := range pubkeys { + if p.Signature.Verify(sigMsg[:], pubkey) { + valid = true + break + } + } + + if !valid { return fmt.Errorf("invalid signature") } return nil } -func decodeForfeitClosure(script []byte) (*secp256k1.PublicKey, error) { - var covenantLessForfeitClosure bitcointree.MultisigClosure +func decodeForfeitClosure(script []byte) ([]*secp256k1.PublicKey, error) { + var forfeit tree.MultisigClosure - if valid, err := covenantLessForfeitClosure.Decode(script); err == nil && valid { - return covenantLessForfeitClosure.Pubkey, nil + valid, err := forfeit.Decode(script) + if err != nil { + return nil, err } - var covenantForfeitClosure tree.CSVSigClosure - if valid, err := covenantForfeitClosure.Decode(script); err == nil && valid { - return covenantForfeitClosure.Pubkey, nil + if !valid { + return nil, fmt.Errorf("invalid forfeit closure script") } - return nil, fmt.Errorf("invalid forfeit closure script") + return forfeit.PubKeys, nil } diff --git a/server/internal/core/application/types.go b/server/internal/core/application/types.go index edfa6bc..c17e080 100644 --- a/server/internal/core/application/types.go +++ b/server/internal/core/application/types.go @@ -45,7 +45,7 @@ type Service interface { ) error GetBoardingAddress( ctx context.Context, userPubkey *secp256k1.PublicKey, - ) (address string, descriptor string, err error) + ) (address string, scripts []string, err error) // Tree signing methods RegisterCosignerPubkey(ctx context.Context, paymentId string, ephemeralPublicKey string) error RegisterCosignerNonces( @@ -62,14 +62,13 @@ type Service interface { } type ServiceInfo struct { - PubKey string - RoundLifetime int64 - UnilateralExitDelay int64 - RoundInterval int64 - Network string - Dust uint64 - BoardingDescriptorTemplate string - ForfeitAddress string + PubKey string + RoundLifetime int64 + UnilateralExitDelay int64 + RoundInterval int64 + Network string + Dust uint64 + ForfeitAddress string } type WalletStatus struct { diff --git a/server/internal/core/ports/tx_builder.go b/server/internal/core/ports/tx_builder.go index 9ce9049..53c195a 100644 --- a/server/internal/core/ports/tx_builder.go +++ b/server/internal/core/ports/tx_builder.go @@ -18,7 +18,7 @@ type SweepInput interface { type Input struct { domain.VtxoKey - Descriptor string + Tapscripts []string } type BoardingInput struct { @@ -49,12 +49,12 @@ type TxBuilder interface { BuildSweepTx(inputs []SweepInput) (signedSweepTx string, err error) GetSweepInput(node tree.Node) (lifetime int64, sweepInput SweepInput, err error) FinalizeAndExtract(tx string) (txhex string, err error) - VerifyTapscriptPartialSigs(tx string) (valid bool, txid string, err error) + VerifyTapscriptPartialSigs(tx string) (valid bool, err error) // FindLeaves returns all the leaves txs that are reachable from the given outpoint FindLeaves(congestionTree tree.CongestionTree, fromtxid string, vout uint32) (leaves []tree.Node, err error) BuildAsyncPaymentTransactions( vtxosToSpend []domain.Vtxo, - descriptors map[domain.VtxoKey]string, + scripts map[domain.VtxoKey][]string, forfeitsLeaves map[domain.VtxoKey]chainhash.Hash, receivers []domain.Receiver, ) (string, error) diff --git a/server/internal/infrastructure/tx-builder/covenant/builder.go b/server/internal/infrastructure/tx-builder/covenant/builder.go index c159e5e..909d25a 100644 --- a/server/internal/infrastructure/tx-builder/covenant/builder.go +++ b/server/internal/infrastructure/tx-builder/covenant/builder.go @@ -3,6 +3,7 @@ package txbuilder import ( "bytes" "context" + "encoding/hex" "fmt" "math" @@ -11,6 +12,7 @@ import ( "github.com/ark-network/ark/server/internal/core/domain" "github.com/ark-network/ark/server/internal/core/ports" "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcwallet/waddrmgr" @@ -122,7 +124,7 @@ func (b *txBuilder) VerifyForfeitTxs(vtxos []domain.Vtxo, connectors []string, f return nil, fmt.Errorf("invalid forfeit tx, expect 2 inputs, got %d", len(pset.Inputs)) } - valid, _, err := b.verifyTapscriptPartialSigs(pset) + valid, err := b.verifyTapscriptPartialSigs(pset) if err != nil { return nil, err } @@ -462,40 +464,66 @@ func (b *txBuilder) GetSweepInput(node tree.Node) (lifetime int64, sweepInput po return lifetime, sweepInput, nil } -func (b *txBuilder) VerifyTapscriptPartialSigs(tx string) (bool, string, error) { +func (b *txBuilder) VerifyTapscriptPartialSigs(tx string) (bool, error) { pset, err := psetv2.NewPsetFromBase64(tx) if err != nil { - return false, "", err + return false, err } return b.verifyTapscriptPartialSigs(pset) } -func (b *txBuilder) verifyTapscriptPartialSigs(pset *psetv2.Pset) (bool, string, error) { +func (b *txBuilder) verifyTapscriptPartialSigs(pset *psetv2.Pset) (bool, error) { utx, _ := pset.UnsignedTx() txid := utx.TxHash().String() + aspPublicKey, err := b.wallet.GetPubkey(context.Background()) + if err != nil { + return false, err + } + for index, input := range pset.Inputs { if len(input.TapLeafScript) == 0 { continue } if input.WitnessUtxo == nil { - return false, txid, fmt.Errorf("missing witness utxo for input %d, cannot verify signature", index) + return false, fmt.Errorf("missing witness utxo for input %d, cannot verify signature", index) } // verify taproot leaf script tapLeaf := input.TapLeafScript[0] + closure, err := tree.DecodeClosure(tapLeaf.Script) + if err != nil { + return false, err + } + + keys := make(map[string]bool) + + switch c := closure.(type) { + case *tree.MultisigClosure: + for _, key := range c.PubKeys { + keys[hex.EncodeToString(schnorr.SerializePubKey(key))] = false + } + case *tree.CSVSigClosure: + for _, key := range c.PubKeys { + keys[hex.EncodeToString(schnorr.SerializePubKey(key))] = false + } + } + + // we don't need to check if ASP signed + keys[hex.EncodeToString(schnorr.SerializePubKey(aspPublicKey))] = true + rootHash := tapLeaf.ControlBlock.RootHash(tapLeaf.Script) tapKeyFromControlBlock := taproot.ComputeTaprootOutputKey(tree.UnspendableKey(), rootHash[:]) pkscript, err := common.P2TRScript(tapKeyFromControlBlock) if err != nil { - return false, txid, err + return false, err } if !bytes.Equal(pkscript, input.WitnessUtxo.Script) { - return false, txid, fmt.Errorf("invalid control block for input %d", index) + return false, fmt.Errorf("invalid control block for input %d", index) } leafHash := taproot.NewBaseTapElementsLeaf(tapLeaf.Script).TapHash() @@ -506,41 +534,93 @@ func (b *txBuilder) verifyTapscriptPartialSigs(pset *psetv2.Pset) (bool, string, &leafHash, ) if err != nil { - return false, txid, err + return false, err } for _, tapScriptSig := range input.TapScriptSig { sig, err := schnorr.ParseSignature(tapScriptSig.Signature) if err != nil { - return false, txid, err + return false, err } pubkey, err := schnorr.ParsePubKey(tapScriptSig.PubKey) if err != nil { - return false, txid, err + return false, err } if !sig.Verify(preimage, pubkey) { - return false, txid, fmt.Errorf("invalid signature for tx %s", txid) + return false, fmt.Errorf("invalid signature for tx %s", txid) } + + keys[hex.EncodeToString(schnorr.SerializePubKey(pubkey))] = true + } + + missingSigs := 0 + for key := range keys { + if !keys[key] { + missingSigs++ + } + } + + if missingSigs > 0 { + return false, fmt.Errorf("missing %d signatures", missingSigs) } } - return true, txid, nil + return true, nil } func (b *txBuilder) FinalizeAndExtract(tx string) (string, error) { - p, err := psetv2.NewPsetFromBase64(tx) + ptx, err := psetv2.NewPsetFromBase64(tx) if err != nil { return "", err } - if err := psetv2.FinalizeAll(p); err != nil { - return "", err + for i, in := range ptx.Inputs { + if in.WitnessUtxo == nil { + return "", fmt.Errorf("missing witness utxo, cannot finalize tx") + } + + if len(in.TapLeafScript) > 0 { + tapLeaf := in.TapLeafScript[0] + + closure, err := tree.DecodeClosure(tapLeaf.Script) + if err != nil { + return "", err + } + + signatures := make(map[string][]byte) + + for _, sig := range in.TapScriptSig { + signatures[hex.EncodeToString(sig.PubKey)] = sig.Signature + } + + controlBlock, err := tapLeaf.ControlBlock.ToBytes() + if err != nil { + return "", err + } + + witness, err := closure.Witness(controlBlock, signatures) + if err != nil { + return "", err + } + + var witnessBuf bytes.Buffer + if err := psbt.WriteTxWitness(&witnessBuf, witness); err != nil { + return "", err + } + + ptx.Inputs[i].FinalScriptWitness = witnessBuf.Bytes() + continue + } + + if err := psetv2.Finalize(ptx, i); err != nil { + return "", fmt.Errorf("failed to finalize signed pset: %s", err) + } } // extract the forfeit tx - extracted, err := psetv2.Extract(p) + extracted, err := psetv2.Extract(ptx) if err != nil { return "", err } @@ -591,7 +671,7 @@ func (b *txBuilder) FindLeaves( func (b *txBuilder) BuildAsyncPaymentTransactions( _ []domain.Vtxo, - _ map[domain.VtxoKey]string, + _ map[domain.VtxoKey][]string, _ map[domain.VtxoKey]chainhash.Hash, _ []domain.Receiver, ) (string, error) { @@ -728,7 +808,7 @@ func (b *txBuilder) createPoolTx( return nil, fmt.Errorf("failed to convert value to bytes: %s", err) } - boardingVtxoScript, err := tree.ParseVtxoScript(in.Descriptor) + boardingVtxoScript, err := tree.ParseVtxoScript(in.Tapscripts) if err != nil { return nil, err } diff --git a/server/internal/infrastructure/tx-builder/covenantless/builder.go b/server/internal/infrastructure/tx-builder/covenantless/builder.go index 1b44577..e8c9db1 100644 --- a/server/internal/infrastructure/tx-builder/covenantless/builder.go +++ b/server/internal/infrastructure/tx-builder/covenantless/builder.go @@ -23,6 +23,7 @@ import ( "github.com/btcsuite/btcwallet/waddrmgr" "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/lntypes" ) type txBuilder struct { @@ -47,47 +48,74 @@ func (b *txBuilder) GetTxID(tx string) (string, error) { return ptx.UnsignedTx.TxHash().String(), nil } -func (b *txBuilder) VerifyTapscriptPartialSigs(tx string) (bool, string, error) { +func (b *txBuilder) VerifyTapscriptPartialSigs(tx string) (bool, error) { ptx, err := psbt.NewFromRawBytes(strings.NewReader(tx), true) if err != nil { - return false, "", err + return false, err } return b.verifyTapscriptPartialSigs(ptx) } -func (b *txBuilder) verifyTapscriptPartialSigs(ptx *psbt.Packet) (bool, string, error) { +func (b *txBuilder) verifyTapscriptPartialSigs(ptx *psbt.Packet) (bool, error) { txid := ptx.UnsignedTx.TxID() + aspPublicKey, err := b.wallet.GetPubkey(context.Background()) + if err != nil { + return false, err + } + for index, input := range ptx.Inputs { if len(input.TaprootLeafScript) == 0 { continue } if input.WitnessUtxo == nil { - return false, txid, fmt.Errorf("missing witness utxo for input %d, cannot verify signature", index) + return false, fmt.Errorf("missing witness utxo for input %d, cannot verify signature", index) } // verify taproot leaf script tapLeaf := input.TaprootLeafScript[0] + + closure, err := tree.DecodeClosure(tapLeaf.Script) + if err != nil { + return false, err + } + + keys := make(map[string]bool) + + switch c := closure.(type) { + case *tree.MultisigClosure: + for _, key := range c.PubKeys { + keys[hex.EncodeToString(schnorr.SerializePubKey(key))] = false + } + case *tree.CSVSigClosure: + for _, key := range c.PubKeys { + keys[hex.EncodeToString(schnorr.SerializePubKey(key))] = false + } + } + + // we don't need to check if ASP signed + keys[hex.EncodeToString(schnorr.SerializePubKey(aspPublicKey))] = true + if len(tapLeaf.ControlBlock) == 0 { - return false, txid, fmt.Errorf("missing control block for input %d", index) + return false, fmt.Errorf("missing control block for input %d", index) } controlBlock, err := txscript.ParseControlBlock(tapLeaf.ControlBlock) if err != nil { - return false, txid, err + return false, err } rootHash := controlBlock.RootHash(tapLeaf.Script) tapKeyFromControlBlock := txscript.ComputeTaprootOutputKey(bitcointree.UnspendableKey(), rootHash[:]) pkscript, err := common.P2TRScript(tapKeyFromControlBlock) if err != nil { - return false, txid, err + return false, err } if !bytes.Equal(pkscript, input.WitnessUtxo.PkScript) { - return false, txid, fmt.Errorf("invalid control block for input %d", index) + return false, fmt.Errorf("invalid control block for input %d", index) } preimage, err := b.getTaprootPreimage( @@ -96,27 +124,40 @@ func (b *txBuilder) verifyTapscriptPartialSigs(ptx *psbt.Packet) (bool, string, tapLeaf.Script, ) if err != nil { - return false, txid, err + return false, err } for _, tapScriptSig := range input.TaprootScriptSpendSig { sig, err := schnorr.ParseSignature(tapScriptSig.Signature) if err != nil { - return false, txid, err + return false, err } pubkey, err := schnorr.ParsePubKey(tapScriptSig.XOnlyPubKey) if err != nil { - return false, txid, err + return false, err } if !sig.Verify(preimage, pubkey) { - return false, txid, fmt.Errorf("invalid signature for tx %s", txid) + return false, fmt.Errorf("invalid signature for tx %s", txid) } + + keys[hex.EncodeToString(schnorr.SerializePubKey(pubkey))] = true + } + + missingSigs := 0 + for key := range keys { + if !keys[key] { + missingSigs++ + } + } + + if missingSigs > 0 { + return false, fmt.Errorf("missing %d signatures", missingSigs) } } - return true, txid, nil + return true, nil } func (b *txBuilder) FinalizeAndExtract(tx string) (string, error) { @@ -128,47 +169,30 @@ func (b *txBuilder) FinalizeAndExtract(tx string) (string, error) { for i, in := range ptx.Inputs { isTaproot := txscript.IsPayToTaproot(in.WitnessUtxo.PkScript) if isTaproot && len(in.TaprootLeafScript) > 0 { - closure, err := bitcointree.DecodeClosure(in.TaprootLeafScript[0].Script) + closure, err := tree.DecodeClosure(in.TaprootLeafScript[0].Script) if err != nil { return "", err } - witness := make(wire.TxWitness, 4) + signatures := make(map[string][]byte) - castClosure, isTaprootMultisig := closure.(*bitcointree.MultisigClosure) - if isTaprootMultisig { - ownerPubkey := schnorr.SerializePubKey(castClosure.Pubkey) - aspKey := schnorr.SerializePubKey(castClosure.AspPubkey) - - for _, sig := range in.TaprootScriptSpendSig { - if bytes.Equal(sig.XOnlyPubKey, ownerPubkey) { - witness[0] = sig.Signature - } - - if bytes.Equal(sig.XOnlyPubKey, aspKey) { - witness[1] = sig.Signature - } - } - - witness[2] = in.TaprootLeafScript[0].Script - witness[3] = in.TaprootLeafScript[0].ControlBlock - - for idw, w := range witness { - if w == nil { - return "", fmt.Errorf("missing witness element %d, cannot finalize taproot mutlisig input %d", idw, i) - } - } - - var witnessBuf bytes.Buffer - - if err := psbt.WriteTxWitness(&witnessBuf, witness); err != nil { - return "", err - } - - ptx.Inputs[i].FinalScriptWitness = witnessBuf.Bytes() - continue + for _, sig := range in.TaprootScriptSpendSig { + signatures[hex.EncodeToString(sig.XOnlyPubKey)] = sig.Signature } + witness, err := closure.Witness(in.TaprootLeafScript[0].ControlBlock, signatures) + if err != nil { + return "", err + } + + var witnessBuf bytes.Buffer + if err := psbt.WriteTxWitness(&witnessBuf, witness); err != nil { + return "", err + } + + ptx.Inputs[i].FinalScriptWitness = witnessBuf.Bytes() + continue + } if err := psbt.Finalize(ptx, i); err != nil { @@ -265,7 +289,7 @@ func (b *txBuilder) VerifyForfeitTxs(vtxos []domain.Vtxo, connectors []string, f return nil, fmt.Errorf("invalid forfeit tx, expect 2 inputs, got %d", len(ptx.Inputs)) } - valid, _, err := b.verifyTapscriptPartialSigs(ptx) + valid, err := b.verifyTapscriptPartialSigs(ptx) if err != nil { return nil, err } @@ -623,7 +647,7 @@ func (b *txBuilder) FindLeaves(congestionTree tree.CongestionTree, fromtxid stri func (b *txBuilder) BuildAsyncPaymentTransactions( vtxos []domain.Vtxo, - descriptors map[domain.VtxoKey]string, + scripts map[domain.VtxoKey][]string, forfeitsLeaves map[domain.VtxoKey]chainhash.Hash, receivers []domain.Receiver, ) (string, error) { @@ -638,9 +662,9 @@ func (b *txBuilder) BuildAsyncPaymentTransactions( redeemTxWeightEstimator := &input.TxWeightEstimator{} for index, vtxo := range vtxos { - desc, ok := descriptors[vtxo.VtxoKey] + vtxoTapscripts, ok := scripts[vtxo.VtxoKey] if !ok { - return "", fmt.Errorf("missing descriptor for vtxo %s", vtxo.VtxoKey) + return "", fmt.Errorf("missing scripts for vtxo %s", vtxo.VtxoKey) } forfeitLeafHash, ok := forfeitsLeaves[vtxo.VtxoKey] @@ -662,7 +686,7 @@ func (b *txBuilder) BuildAsyncPaymentTransactions( Index: vtxo.VOut, } - vtxoScript, err := bitcointree.ParseVtxoScript(desc) + vtxoScript, err := bitcointree.ParseVtxoScript(vtxoTapscripts) if err != nil { return "", err } @@ -698,7 +722,12 @@ func (b *txBuilder) BuildAsyncPaymentTransactions( return "", err } - redeemTxWeightEstimator.AddTapscriptInput(64*2+40, &waddrmgr.Tapscript{ + closure, err := tree.DecodeClosure(leafProof.Script) + if err != nil { + return "", err + } + + redeemTxWeightEstimator.AddTapscriptInput(lntypes.WeightUnit(closure.WitnessSize()), &waddrmgr.Tapscript{ RevealedScript: leafProof.Script, ControlBlock: ctrlBlock, }) @@ -940,7 +969,7 @@ func (b *txBuilder) createRoundTx( }) nSequences = append(nSequences, wire.MaxTxInSequenceNum) - boardingVtxoScript, err := bitcointree.ParseVtxoScript(boardingInput.Descriptor) + boardingVtxoScript, err := bitcointree.ParseVtxoScript(boardingInput.Tapscripts) if err != nil { return nil, err } @@ -1322,7 +1351,7 @@ func castToOutpoints(inputs []ports.TxInput) []ports.TxOutpoint { func extractSweepLeaf(input psbt.PInput) (sweepLeaf *psbt.TaprootTapLeafScript, internalKey *secp256k1.PublicKey, lifetime int64, err error) { for _, leaf := range input.TaprootLeafScript { - closure := &bitcointree.CSVSigClosure{} + closure := &tree.CSVSigClosure{} valid, err := closure.Decode(leaf.Script) if err != nil { return nil, nil, 0, err diff --git a/server/internal/infrastructure/tx-builder/covenantless/sweep.go b/server/internal/infrastructure/tx-builder/covenantless/sweep.go index f9029c8..2d23582 100644 --- a/server/internal/infrastructure/tx-builder/covenantless/sweep.go +++ b/server/internal/infrastructure/tx-builder/covenantless/sweep.go @@ -5,7 +5,7 @@ import ( "fmt" "github.com/ark-network/ark/common" - "github.com/ark-network/ark/common/bitcointree" + "github.com/ark-network/ark/common/tree" "github.com/ark-network/ark/server/internal/core/ports" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" @@ -27,7 +27,7 @@ func sweepTransaction( Index: input.GetIndex(), }) - sweepClosure := bitcointree.CSVSigClosure{} + sweepClosure := tree.CSVSigClosure{} valid, err := sweepClosure.Decode(input.GetLeafScript()) if err != nil { return nil, err diff --git a/server/internal/infrastructure/wallet/btc-embedded/wallet.go b/server/internal/infrastructure/wallet/btc-embedded/wallet.go index 223224d..4388e7a 100644 --- a/server/internal/infrastructure/wallet/btc-embedded/wallet.go +++ b/server/internal/infrastructure/wallet/btc-embedded/wallet.go @@ -10,7 +10,7 @@ import ( "time" "github.com/ark-network/ark/common" - "github.com/ark-network/ark/common/bitcointree" + "github.com/ark-network/ark/common/tree" "github.com/ark-network/ark/server/internal/core/domain" "github.com/ark-network/ark/server/internal/core/ports" "github.com/btcsuite/btcd/btcec/v2/schnorr" @@ -745,46 +745,29 @@ func (s *service) SignTransaction(ctx context.Context, partialTx string, extract for i, in := range ptx.Inputs { isTaproot := txscript.IsPayToTaproot(in.WitnessUtxo.PkScript) if isTaproot && len(in.TaprootLeafScript) > 0 { - closure, err := bitcointree.DecodeClosure(in.TaprootLeafScript[0].Script) + closure, err := tree.DecodeClosure(in.TaprootLeafScript[0].Script) if err != nil { return "", err } - witness := make(wire.TxWitness, 4) + signatures := make(map[string][]byte) - castClosure, isTaprootMultisig := closure.(*bitcointree.MultisigClosure) - if isTaprootMultisig { - ownerPubkey := schnorr.SerializePubKey(castClosure.Pubkey) - aspKey := schnorr.SerializePubKey(castClosure.AspPubkey) - - for _, sig := range in.TaprootScriptSpendSig { - if bytes.Equal(sig.XOnlyPubKey, ownerPubkey) { - witness[0] = sig.Signature - } - - if bytes.Equal(sig.XOnlyPubKey, aspKey) { - witness[1] = sig.Signature - } - } - - witness[2] = in.TaprootLeafScript[0].Script - witness[3] = in.TaprootLeafScript[0].ControlBlock - - for idw, w := range witness { - if w == nil { - return "", fmt.Errorf("missing witness element %d, cannot finalize taproot mutlisig input %d", idw, i) - } - } - - var witnessBuf bytes.Buffer - - if err := psbt.WriteTxWitness(&witnessBuf, witness); err != nil { - return "", err - } - - ptx.Inputs[i].FinalScriptWitness = witnessBuf.Bytes() - continue + for _, sig := range in.TaprootScriptSpendSig { + signatures[hex.EncodeToString(sig.XOnlyPubKey)] = sig.Signature } + + witness, err := closure.Witness(in.TaprootLeafScript[0].ControlBlock, signatures) + if err != nil { + return "", err + } + + var witnessBuf bytes.Buffer + if err := psbt.WriteTxWitness(&witnessBuf, witness); err != nil { + return "", err + } + + ptx.Inputs[i].FinalScriptWitness = witnessBuf.Bytes() + continue } if err := psbt.Finalize(ptx, i); err != nil { diff --git a/server/internal/infrastructure/wallet/liquid-standalone/transaction.go b/server/internal/infrastructure/wallet/liquid-standalone/transaction.go index 81bf981..a857c36 100644 --- a/server/internal/infrastructure/wallet/liquid-standalone/transaction.go +++ b/server/internal/infrastructure/wallet/liquid-standalone/transaction.go @@ -12,7 +12,6 @@ import ( pb "github.com/ark-network/ark/api-spec/protobuf/gen/ocean/v1" "github.com/ark-network/ark/common/tree" "github.com/ark-network/ark/server/internal/core/ports" - "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" @@ -58,43 +57,29 @@ func (s *service) SignTransaction( return "", err } - switch c := closure.(type) { - case *tree.MultisigClosure: - asp := schnorr.SerializePubKey(c.AspPubkey) - owner := schnorr.SerializePubKey(c.Pubkey) + signatures := make(map[string][]byte) - witness := make([][]byte, 4) - for _, sig := range in.TapScriptSig { - if bytes.Equal(sig.PubKey, owner) { - witness[0] = sig.Signature - continue - } - - if bytes.Equal(sig.PubKey, asp) { - witness[1] = sig.Signature - } - } - - witness[2] = tapLeaf.Script - - controlBlock, err := tapLeaf.ControlBlock.ToBytes() - if err != nil { - return "", err - } - - witness[3] = controlBlock - - var witnessBuf bytes.Buffer - - if err := psbt.WriteTxWitness(&witnessBuf, witness); err != nil { - return "", err - } - - ptx.Inputs[i].FinalScriptWitness = witnessBuf.Bytes() - continue - default: - return "", fmt.Errorf("unexpected closure type %T", c) + for _, sig := range in.TapScriptSig { + signatures[hex.EncodeToString(sig.PubKey)] = sig.Signature } + + controlBlock, err := tapLeaf.ControlBlock.ToBytes() + if err != nil { + return "", err + } + + witness, err := closure.Witness(controlBlock, signatures) + if err != nil { + return "", err + } + + var witnessBuf bytes.Buffer + if err := psbt.WriteTxWitness(&witnessBuf, witness); err != nil { + return "", err + } + + ptx.Inputs[i].FinalScriptWitness = witnessBuf.Bytes() + continue } if err := psetv2.Finalize(ptx, i); err != nil { diff --git a/server/internal/interface/grpc/handlers/arkservice.go b/server/internal/interface/grpc/handlers/arkservice.go index 0e17634..080ea69 100644 --- a/server/internal/interface/grpc/handlers/arkservice.go +++ b/server/internal/interface/grpc/handlers/arkservice.go @@ -3,9 +3,12 @@ package handlers import ( "context" "encoding/hex" + "fmt" "sync" 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/descriptor" "github.com/ark-network/ark/server/internal/core/application" "github.com/ark-network/ark/server/internal/core/domain" "github.com/decred/dcrd/dcrec/secp256k1/v4" @@ -43,6 +46,15 @@ func (h *handler) GetInfo( return nil, err } + desc := fmt.Sprintf( + descriptor.DefaultVtxoDescriptorTemplate, + hex.EncodeToString(bitcointree.UnspendableKey().SerializeCompressed()), + "USER", + info.PubKey, + info.UnilateralExitDelay, + info.PubKey, + ) + return &arkv1.GetInfoResponse{ Pubkey: info.PubKey, RoundLifetime: info.RoundLifetime, @@ -50,8 +62,9 @@ func (h *handler) GetInfo( RoundInterval: info.RoundInterval, Network: info.Network, Dust: int64(info.Dust), - BoardingDescriptorTemplate: info.BoardingDescriptorTemplate, ForfeitAddress: info.ForfeitAddress, + BoardingDescriptorTemplate: desc, + VtxoDescriptorTemplates: []string{desc}, }, nil } @@ -73,14 +86,18 @@ func (h *handler) GetBoardingAddress( return nil, status.Error(codes.InvalidArgument, "invalid pubkey (parse error)") } - addr, descriptor, err := h.svc.GetBoardingAddress(ctx, userPubkey) + addr, tapscripts, err := h.svc.GetBoardingAddress(ctx, userPubkey) if err != nil { return nil, err } return &arkv1.GetBoardingAddressResponse{ - Address: addr, - Descriptor_: descriptor, + Address: addr, + TaprootTree: &arkv1.GetBoardingAddressResponse_Tapscripts{ + Tapscripts: &arkv1.Tapscripts{ + Scripts: tapscripts, + }, + }, }, nil } diff --git a/server/internal/interface/grpc/handlers/parser.go b/server/internal/interface/grpc/handlers/parser.go index cac9a90..e3231c8 100644 --- a/server/internal/interface/grpc/handlers/parser.go +++ b/server/internal/interface/grpc/handlers/parser.go @@ -43,7 +43,7 @@ func parseAsyncPaymentInputs(ins []*arkv1.AsyncPaymentInput) ([]application.Asyn Txid: input.GetInput().GetOutpoint().GetTxid(), VOut: input.GetInput().GetOutpoint().GetVout(), }, - Descriptor: input.GetInput().GetDescriptor_(), + Tapscripts: input.GetInput().GetTapscripts().GetScripts(), }, ForfeitLeafHash: *forfeitLeafHash, }) @@ -82,7 +82,7 @@ func parseInputs(ins []*arkv1.Input) ([]ports.Input, error) { Txid: input.GetOutpoint().GetTxid(), VOut: input.GetOutpoint().GetVout(), }, - Descriptor: input.GetDescriptor_(), + Tapscripts: input.GetTapscripts().GetScripts(), }) }