From 3ab2b57835133e6137536a76cd2248061c465bf9 Mon Sep 17 00:00:00 2001 From: Trinity Date: Fri, 7 Nov 2025 11:16:19 +0700 Subject: [PATCH] complete transfer v1 -> v1 --- nockchain/types.pb.go | 359 ++++++++++++++++++++++++++++++++--------- proto/types.proto | 31 +++- wallet/nockhash.go | 205 +++++++++++++++-------- wallet/noun.go | 65 ++++++++ wallet/service.go | 248 ++++++++++++++++------------ wallet/service_test.go | 172 +++++--------------- wallet/types.go | 138 +++++++++++++--- 7 files changed, 818 insertions(+), 400 deletions(-) diff --git a/nockchain/types.pb.go b/nockchain/types.pb.go index bef06d2..dda66f7 100644 --- a/nockchain/types.pb.go +++ b/nockchain/types.pb.go @@ -478,7 +478,7 @@ func (*NockchainNamedSpend_Witness) isNockchainNamedSpend_SpendKind() {} type NockchainSpendV0 struct { state protoimpl.MessageState `protogen:"open.v1"` Signatures []*NockchainSignature `protobuf:"bytes,1,rep,name=signatures,proto3" json:"signatures,omitempty"` - Seeds []*NockchainSeedV0 `protobuf:"bytes,2,rep,name=seeds,proto3" json:"seeds,omitempty"` + Seeds []*NockchainSeed `protobuf:"bytes,2,rep,name=seeds,proto3" json:"seeds,omitempty"` Fee uint64 `protobuf:"varint,3,opt,name=fee,proto3" json:"fee,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -521,7 +521,7 @@ func (x *NockchainSpendV0) GetSignatures() []*NockchainSignature { return nil } -func (x *NockchainSpendV0) GetSeeds() []*NockchainSeedV0 { +func (x *NockchainSpendV0) GetSeeds() []*NockchainSeed { if x != nil { return x.Seeds } @@ -537,6 +537,9 @@ func (x *NockchainSpendV0) GetFee() uint64 { type NockchainSpendV1 struct { state protoimpl.MessageState `protogen:"open.v1"` + Witness []*NockchainWitness `protobuf:"bytes,1,rep,name=witness,proto3" json:"witness,omitempty"` + Seeds []*NockchainSeed `protobuf:"bytes,2,rep,name=seeds,proto3" json:"seeds,omitempty"` + Fee uint64 `protobuf:"varint,3,opt,name=fee,proto3" json:"fee,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -571,6 +574,27 @@ func (*NockchainSpendV1) Descriptor() ([]byte, []int) { return file_types_proto_rawDescGZIP(), []int{7} } +func (x *NockchainSpendV1) GetWitness() []*NockchainWitness { + if x != nil { + return x.Witness + } + return nil +} + +func (x *NockchainSpendV1) GetSeeds() []*NockchainSeed { + if x != nil { + return x.Seeds + } + return nil +} + +func (x *NockchainSpendV1) GetFee() uint64 { + if x != nil { + return x.Fee + } + return 0 +} + type NockchainNote struct { state protoimpl.MessageState `protogen:"open.v1"` // Types that are valid to be assigned to Note: @@ -658,9 +682,9 @@ type NockchainNoteV0 struct { Version Version `protobuf:"varint,1,opt,name=version,proto3,enum=nockchain.public.v2.Version" json:"version,omitempty"` OriginPage uint64 `protobuf:"varint,2,opt,name=origin_page,json=originPage,proto3" json:"origin_page,omitempty"` Name *NockchainName `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` - Lock *NockchainLock `protobuf:"bytes,5,opt,name=lock,proto3" json:"lock,omitempty"` - Source *NockchainSource `protobuf:"bytes,6,opt,name=source,proto3" json:"source,omitempty"` - Asset uint64 `protobuf:"varint,7,opt,name=asset,proto3" json:"asset,omitempty"` + Lock *NockchainLock `protobuf:"bytes,4,opt,name=lock,proto3" json:"lock,omitempty"` + Source *NockchainSource `protobuf:"bytes,5,opt,name=source,proto3" json:"source,omitempty"` + Asset uint64 `protobuf:"varint,6,opt,name=asset,proto3" json:"asset,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -739,6 +763,11 @@ func (x *NockchainNoteV0) GetAsset() uint64 { type NockchainNoteV1 struct { state protoimpl.MessageState `protogen:"open.v1"` + Version Version `protobuf:"varint,1,opt,name=version,proto3,enum=nockchain.public.v2.Version" json:"version,omitempty"` + OriginPage uint64 `protobuf:"varint,2,opt,name=origin_page,json=originPage,proto3" json:"origin_page,omitempty"` + Name *NockchainName `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + NoteData *NockchainLock `protobuf:"bytes,4,opt,name=note_data,json=noteData,proto3" json:"note_data,omitempty"` + Assets uint64 `protobuf:"varint,5,opt,name=assets,proto3" json:"assets,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -773,6 +802,41 @@ func (*NockchainNoteV1) Descriptor() ([]byte, []int) { return file_types_proto_rawDescGZIP(), []int{10} } +func (x *NockchainNoteV1) GetVersion() Version { + if x != nil { + return x.Version + } + return Version_V0 +} + +func (x *NockchainNoteV1) GetOriginPage() uint64 { + if x != nil { + return x.OriginPage + } + return 0 +} + +func (x *NockchainNoteV1) GetName() *NockchainName { + if x != nil { + return x.Name + } + return nil +} + +func (x *NockchainNoteV1) GetNoteData() *NockchainLock { + if x != nil { + return x.NoteData + } + return nil +} + +func (x *NockchainNoteV1) GetAssets() uint64 { + if x != nil { + return x.Assets + } + return 0 +} + type NockchainName struct { state protoimpl.MessageState `protogen:"open.v1"` First string `protobuf:"bytes,1,opt,name=first,proto3" json:"first,omitempty"` @@ -885,31 +949,28 @@ func (x *NockchainSignature) GetSig() []uint64 { return nil } -type NockchainSeedV0 struct { - state protoimpl.MessageState `protogen:"open.v1"` - OutputSource *NockchainSource `protobuf:"bytes,1,opt,name=output_source,json=outputSource,proto3,oneof" json:"output_source,omitempty"` - LockRoot string `protobuf:"bytes,2,opt,name=lock_root,json=lockRoot,proto3" json:"lock_root,omitempty"` - NoteData *NockchainLock `protobuf:"bytes,3,opt,name=note_data,json=noteData,proto3" json:"note_data,omitempty"` - Gift uint64 `protobuf:"varint,4,opt,name=gift,proto3" json:"gift,omitempty"` - ParentHash string `protobuf:"bytes,5,opt,name=parent_hash,json=parentHash,proto3" json:"parent_hash,omitempty"` +type NockchainWitness struct { + state protoimpl.MessageState `protogen:"open.v1"` + Lmp *NockchainLockMerkleProof `protobuf:"bytes,1,opt,name=lmp,proto3" json:"lmp,omitempty"` + Pkh []*NockchainSignature `protobuf:"bytes,2,rep,name=pkh,proto3" json:"pkh,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *NockchainSeedV0) Reset() { - *x = NockchainSeedV0{} +func (x *NockchainWitness) Reset() { + *x = NockchainWitness{} mi := &file_types_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *NockchainSeedV0) String() string { +func (x *NockchainWitness) String() string { return protoimpl.X.MessageStringOf(x) } -func (*NockchainSeedV0) ProtoMessage() {} +func (*NockchainWitness) ProtoMessage() {} -func (x *NockchainSeedV0) ProtoReflect() protoreflect.Message { +func (x *NockchainWitness) ProtoReflect() protoreflect.Message { mi := &file_types_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -921,40 +982,155 @@ func (x *NockchainSeedV0) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use NockchainSeedV0.ProtoReflect.Descriptor instead. -func (*NockchainSeedV0) Descriptor() ([]byte, []int) { +// Deprecated: Use NockchainWitness.ProtoReflect.Descriptor instead. +func (*NockchainWitness) Descriptor() ([]byte, []int) { return file_types_proto_rawDescGZIP(), []int{13} } -func (x *NockchainSeedV0) GetOutputSource() *NockchainSource { +func (x *NockchainWitness) GetLmp() *NockchainLockMerkleProof { + if x != nil { + return x.Lmp + } + return nil +} + +func (x *NockchainWitness) GetPkh() []*NockchainSignature { + if x != nil { + return x.Pkh + } + return nil +} + +type NockchainLockMerkleProof struct { + state protoimpl.MessageState `protogen:"open.v1"` + SpendCondition *NockchainLock `protobuf:"bytes,1,opt,name=spend_condition,json=spendCondition,proto3" json:"spend_condition,omitempty"` + Axis uint64 `protobuf:"varint,2,opt,name=axis,proto3" json:"axis,omitempty"` + MerkleRoot string `protobuf:"bytes,3,opt,name=merkle_root,json=merkleRoot,proto3" json:"merkle_root,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NockchainLockMerkleProof) Reset() { + *x = NockchainLockMerkleProof{} + mi := &file_types_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NockchainLockMerkleProof) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NockchainLockMerkleProof) ProtoMessage() {} + +func (x *NockchainLockMerkleProof) ProtoReflect() protoreflect.Message { + mi := &file_types_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NockchainLockMerkleProof.ProtoReflect.Descriptor instead. +func (*NockchainLockMerkleProof) Descriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{14} +} + +func (x *NockchainLockMerkleProof) GetSpendCondition() *NockchainLock { + if x != nil { + return x.SpendCondition + } + return nil +} + +func (x *NockchainLockMerkleProof) GetAxis() uint64 { + if x != nil { + return x.Axis + } + return 0 +} + +func (x *NockchainLockMerkleProof) GetMerkleRoot() string { + if x != nil { + return x.MerkleRoot + } + return "" +} + +type NockchainSeed struct { + state protoimpl.MessageState `protogen:"open.v1"` + OutputSource *NockchainSource `protobuf:"bytes,1,opt,name=output_source,json=outputSource,proto3,oneof" json:"output_source,omitempty"` + LockRoot string `protobuf:"bytes,2,opt,name=lock_root,json=lockRoot,proto3" json:"lock_root,omitempty"` + NoteData *NockchainLock `protobuf:"bytes,3,opt,name=note_data,json=noteData,proto3" json:"note_data,omitempty"` + Gift uint64 `protobuf:"varint,4,opt,name=gift,proto3" json:"gift,omitempty"` + ParentHash string `protobuf:"bytes,5,opt,name=parent_hash,json=parentHash,proto3" json:"parent_hash,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NockchainSeed) Reset() { + *x = NockchainSeed{} + mi := &file_types_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NockchainSeed) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NockchainSeed) ProtoMessage() {} + +func (x *NockchainSeed) ProtoReflect() protoreflect.Message { + mi := &file_types_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NockchainSeed.ProtoReflect.Descriptor instead. +func (*NockchainSeed) Descriptor() ([]byte, []int) { + return file_types_proto_rawDescGZIP(), []int{15} +} + +func (x *NockchainSeed) GetOutputSource() *NockchainSource { if x != nil { return x.OutputSource } return nil } -func (x *NockchainSeedV0) GetLockRoot() string { +func (x *NockchainSeed) GetLockRoot() string { if x != nil { return x.LockRoot } return "" } -func (x *NockchainSeedV0) GetNoteData() *NockchainLock { +func (x *NockchainSeed) GetNoteData() *NockchainLock { if x != nil { return x.NoteData } return nil } -func (x *NockchainSeedV0) GetGift() uint64 { +func (x *NockchainSeed) GetGift() uint64 { if x != nil { return x.Gift } return 0 } -func (x *NockchainSeedV0) GetParentHash() string { +func (x *NockchainSeed) GetParentHash() string { if x != nil { return x.ParentHash } @@ -971,7 +1147,7 @@ type NockchainLock struct { func (x *NockchainLock) Reset() { *x = NockchainLock{} - mi := &file_types_proto_msgTypes[14] + mi := &file_types_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -983,7 +1159,7 @@ func (x *NockchainLock) String() string { func (*NockchainLock) ProtoMessage() {} func (x *NockchainLock) ProtoReflect() protoreflect.Message { - mi := &file_types_proto_msgTypes[14] + mi := &file_types_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -996,7 +1172,7 @@ func (x *NockchainLock) ProtoReflect() protoreflect.Message { // Deprecated: Use NockchainLock.ProtoReflect.Descriptor instead. func (*NockchainLock) Descriptor() ([]byte, []int) { - return file_types_proto_rawDescGZIP(), []int{14} + return file_types_proto_rawDescGZIP(), []int{16} } func (x *NockchainLock) GetKeysRequired() uint64 { @@ -1023,7 +1199,7 @@ type NockchainSource struct { func (x *NockchainSource) Reset() { *x = NockchainSource{} - mi := &file_types_proto_msgTypes[15] + mi := &file_types_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1035,7 +1211,7 @@ func (x *NockchainSource) String() string { func (*NockchainSource) ProtoMessage() {} func (x *NockchainSource) ProtoReflect() protoreflect.Message { - mi := &file_types_proto_msgTypes[15] + mi := &file_types_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1048,7 +1224,7 @@ func (x *NockchainSource) ProtoReflect() protoreflect.Message { // Deprecated: Use NockchainSource.ProtoReflect.Descriptor instead. func (*NockchainSource) Descriptor() ([]byte, []int) { - return file_types_proto_rawDescGZIP(), []int{15} + return file_types_proto_rawDescGZIP(), []int{17} } func (x *NockchainSource) GetSource() string { @@ -1094,14 +1270,17 @@ const file_types_proto_rawDesc = "" + "\x06legacy\x18\x02 \x01(\v2%.nockchain.public.v2.NockchainSpendV0H\x00R\x06legacy\x12A\n" + "\awitness\x18\x03 \x01(\v2%.nockchain.public.v2.NockchainSpendV1H\x00R\awitnessB\f\n" + "\n" + - "spend_kind\"\xa9\x01\n" + + "spend_kind\"\xa7\x01\n" + "\x10NockchainSpendV0\x12G\n" + "\n" + "signatures\x18\x01 \x03(\v2'.nockchain.public.v2.NockchainSignatureR\n" + - "signatures\x12:\n" + - "\x05seeds\x18\x02 \x03(\v2$.nockchain.public.v2.NockchainSeedV0R\x05seeds\x12\x10\n" + - "\x03fee\x18\x03 \x01(\x04R\x03fee\"\x12\n" + - "\x10NockchainSpendV1\"\x87\x01\n" + + "signatures\x128\n" + + "\x05seeds\x18\x02 \x03(\v2\".nockchain.public.v2.NockchainSeedR\x05seeds\x12\x10\n" + + "\x03fee\x18\x03 \x01(\x04R\x03fee\"\x9f\x01\n" + + "\x10NockchainSpendV1\x12?\n" + + "\awitness\x18\x01 \x03(\v2%.nockchain.public.v2.NockchainWitnessR\awitness\x128\n" + + "\x05seeds\x18\x02 \x03(\v2\".nockchain.public.v2.NockchainSeedR\x05seeds\x12\x10\n" + + "\x03fee\x18\x03 \x01(\x04R\x03fee\"\x87\x01\n" + "\rNockchainNote\x126\n" + "\x02v0\x18\x01 \x01(\v2$.nockchain.public.v2.NockchainNoteV0H\x00R\x02v0\x126\n" + "\x02v1\x18\x02 \x01(\v2$.nockchain.public.v2.NockchainNoteV1H\x00R\x02v1B\x06\n" + @@ -1111,18 +1290,32 @@ const file_types_proto_rawDesc = "" + "\vorigin_page\x18\x02 \x01(\x04R\n" + "originPage\x126\n" + "\x04name\x18\x03 \x01(\v2\".nockchain.public.v2.NockchainNameR\x04name\x126\n" + - "\x04lock\x18\x05 \x01(\v2\".nockchain.public.v2.NockchainLockR\x04lock\x12<\n" + - "\x06source\x18\x06 \x01(\v2$.nockchain.public.v2.NockchainSourceR\x06source\x12\x14\n" + - "\x05asset\x18\a \x01(\x04R\x05asset\"\x11\n" + - "\x0fNockchainNoteV1\"9\n" + + "\x04lock\x18\x04 \x01(\v2\".nockchain.public.v2.NockchainLockR\x04lock\x12<\n" + + "\x06source\x18\x05 \x01(\v2$.nockchain.public.v2.NockchainSourceR\x06source\x12\x14\n" + + "\x05asset\x18\x06 \x01(\x04R\x05asset\"\xfb\x01\n" + + "\x0fNockchainNoteV1\x126\n" + + "\aversion\x18\x01 \x01(\x0e2\x1c.nockchain.public.v2.VersionR\aversion\x12\x1f\n" + + "\vorigin_page\x18\x02 \x01(\x04R\n" + + "originPage\x126\n" + + "\x04name\x18\x03 \x01(\v2\".nockchain.public.v2.NockchainNameR\x04name\x12?\n" + + "\tnote_data\x18\x04 \x01(\v2\".nockchain.public.v2.NockchainLockR\bnoteData\x12\x16\n" + + "\x06assets\x18\x05 \x01(\x04R\x06assets\"9\n" + "\rNockchainName\x12\x14\n" + "\x05first\x18\x01 \x01(\tR\x05first\x12\x12\n" + "\x04last\x18\x02 \x01(\tR\x04last\"R\n" + "\x12NockchainSignature\x12\x16\n" + "\x06pubkey\x18\x01 \x01(\tR\x06pubkey\x12\x12\n" + "\x04chal\x18\x02 \x03(\x04R\x04chal\x12\x10\n" + - "\x03sig\x18\x03 \x03(\x04R\x03sig\"\x86\x02\n" + - "\x0fNockchainSeedV0\x12N\n" + + "\x03sig\x18\x03 \x03(\x04R\x03sig\"\x8e\x01\n" + + "\x10NockchainWitness\x12?\n" + + "\x03lmp\x18\x01 \x01(\v2-.nockchain.public.v2.NockchainLockMerkleProofR\x03lmp\x129\n" + + "\x03pkh\x18\x02 \x03(\v2'.nockchain.public.v2.NockchainSignatureR\x03pkh\"\x9c\x01\n" + + "\x18NockchainLockMerkleProof\x12K\n" + + "\x0fspend_condition\x18\x01 \x01(\v2\".nockchain.public.v2.NockchainLockR\x0espendCondition\x12\x12\n" + + "\x04axis\x18\x02 \x01(\x04R\x04axis\x12\x1f\n" + + "\vmerkle_root\x18\x03 \x01(\tR\n" + + "merkleRoot\"\x84\x02\n" + + "\rNockchainSeed\x12N\n" + "\routput_source\x18\x01 \x01(\v2$.nockchain.public.v2.NockchainSourceH\x00R\foutputSource\x88\x01\x01\x12\x1b\n" + "\tlock_root\x18\x02 \x01(\tR\blockRoot\x12?\n" + "\tnote_data\x18\x03 \x01(\v2\".nockchain.public.v2.NockchainLockR\bnoteData\x12\x12\n" + @@ -1164,30 +1357,32 @@ func file_types_proto_rawDescGZIP() []byte { } var file_types_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_types_proto_msgTypes = make([]protoimpl.MessageInfo, 16) +var file_types_proto_msgTypes = make([]protoimpl.MessageInfo, 18) var file_types_proto_goTypes = []any{ - (ImportType)(0), // 0: nockchain.public.v2.ImportType - (Version)(0), // 1: nockchain.public.v2.Version - (*ScanData)(nil), // 2: nockchain.public.v2.ScanData - (*TimelockIntent)(nil), // 3: nockchain.public.v2.TimelockIntent - (*TimelockRange)(nil), // 4: nockchain.public.v2.TimelockRange - (*Timelock)(nil), // 5: nockchain.public.v2.Timelock - (*RawTx)(nil), // 6: nockchain.public.v2.RawTx - (*NockchainNamedSpend)(nil), // 7: nockchain.public.v2.NockchainNamedSpend - (*NockchainSpendV0)(nil), // 8: nockchain.public.v2.NockchainSpendV0 - (*NockchainSpendV1)(nil), // 9: nockchain.public.v2.NockchainSpendV1 - (*NockchainNote)(nil), // 10: nockchain.public.v2.NockchainNote - (*NockchainNoteV0)(nil), // 11: nockchain.public.v2.NockchainNoteV0 - (*NockchainNoteV1)(nil), // 12: nockchain.public.v2.NockchainNoteV1 - (*NockchainName)(nil), // 13: nockchain.public.v2.NockchainName - (*NockchainSignature)(nil), // 14: nockchain.public.v2.NockchainSignature - (*NockchainSeedV0)(nil), // 15: nockchain.public.v2.NockchainSeedV0 - (*NockchainLock)(nil), // 16: nockchain.public.v2.NockchainLock - (*NockchainSource)(nil), // 17: nockchain.public.v2.NockchainSource - (*Balance)(nil), // 18: nockchain.public.v2.Balance + (ImportType)(0), // 0: nockchain.public.v2.ImportType + (Version)(0), // 1: nockchain.public.v2.Version + (*ScanData)(nil), // 2: nockchain.public.v2.ScanData + (*TimelockIntent)(nil), // 3: nockchain.public.v2.TimelockIntent + (*TimelockRange)(nil), // 4: nockchain.public.v2.TimelockRange + (*Timelock)(nil), // 5: nockchain.public.v2.Timelock + (*RawTx)(nil), // 6: nockchain.public.v2.RawTx + (*NockchainNamedSpend)(nil), // 7: nockchain.public.v2.NockchainNamedSpend + (*NockchainSpendV0)(nil), // 8: nockchain.public.v2.NockchainSpendV0 + (*NockchainSpendV1)(nil), // 9: nockchain.public.v2.NockchainSpendV1 + (*NockchainNote)(nil), // 10: nockchain.public.v2.NockchainNote + (*NockchainNoteV0)(nil), // 11: nockchain.public.v2.NockchainNoteV0 + (*NockchainNoteV1)(nil), // 12: nockchain.public.v2.NockchainNoteV1 + (*NockchainName)(nil), // 13: nockchain.public.v2.NockchainName + (*NockchainSignature)(nil), // 14: nockchain.public.v2.NockchainSignature + (*NockchainWitness)(nil), // 15: nockchain.public.v2.NockchainWitness + (*NockchainLockMerkleProof)(nil), // 16: nockchain.public.v2.NockchainLockMerkleProof + (*NockchainSeed)(nil), // 17: nockchain.public.v2.NockchainSeed + (*NockchainLock)(nil), // 18: nockchain.public.v2.NockchainLock + (*NockchainSource)(nil), // 19: nockchain.public.v2.NockchainSource + (*Balance)(nil), // 20: nockchain.public.v2.Balance } var file_types_proto_depIdxs = []int32{ - 18, // 0: nockchain.public.v2.ScanData.data:type_name -> nockchain.public.v2.Balance + 20, // 0: nockchain.public.v2.ScanData.data:type_name -> nockchain.public.v2.Balance 4, // 1: nockchain.public.v2.TimelockIntent.absolute:type_name -> nockchain.public.v2.TimelockRange 4, // 2: nockchain.public.v2.TimelockIntent.relative:type_name -> nockchain.public.v2.TimelockRange 5, // 3: nockchain.public.v2.TimelockRange.min:type_name -> nockchain.public.v2.Timelock @@ -1198,20 +1393,28 @@ var file_types_proto_depIdxs = []int32{ 8, // 8: nockchain.public.v2.NockchainNamedSpend.legacy:type_name -> nockchain.public.v2.NockchainSpendV0 9, // 9: nockchain.public.v2.NockchainNamedSpend.witness:type_name -> nockchain.public.v2.NockchainSpendV1 14, // 10: nockchain.public.v2.NockchainSpendV0.signatures:type_name -> nockchain.public.v2.NockchainSignature - 15, // 11: nockchain.public.v2.NockchainSpendV0.seeds:type_name -> nockchain.public.v2.NockchainSeedV0 - 11, // 12: nockchain.public.v2.NockchainNote.v0:type_name -> nockchain.public.v2.NockchainNoteV0 - 12, // 13: nockchain.public.v2.NockchainNote.v1:type_name -> nockchain.public.v2.NockchainNoteV1 - 1, // 14: nockchain.public.v2.NockchainNoteV0.version:type_name -> nockchain.public.v2.Version - 13, // 15: nockchain.public.v2.NockchainNoteV0.name:type_name -> nockchain.public.v2.NockchainName - 16, // 16: nockchain.public.v2.NockchainNoteV0.lock:type_name -> nockchain.public.v2.NockchainLock - 17, // 17: nockchain.public.v2.NockchainNoteV0.source:type_name -> nockchain.public.v2.NockchainSource - 17, // 18: nockchain.public.v2.NockchainSeedV0.output_source:type_name -> nockchain.public.v2.NockchainSource - 16, // 19: nockchain.public.v2.NockchainSeedV0.note_data:type_name -> nockchain.public.v2.NockchainLock - 20, // [20:20] is the sub-list for method output_type - 20, // [20:20] is the sub-list for method input_type - 20, // [20:20] is the sub-list for extension type_name - 20, // [20:20] is the sub-list for extension extendee - 0, // [0:20] is the sub-list for field type_name + 17, // 11: nockchain.public.v2.NockchainSpendV0.seeds:type_name -> nockchain.public.v2.NockchainSeed + 15, // 12: nockchain.public.v2.NockchainSpendV1.witness:type_name -> nockchain.public.v2.NockchainWitness + 17, // 13: nockchain.public.v2.NockchainSpendV1.seeds:type_name -> nockchain.public.v2.NockchainSeed + 11, // 14: nockchain.public.v2.NockchainNote.v0:type_name -> nockchain.public.v2.NockchainNoteV0 + 12, // 15: nockchain.public.v2.NockchainNote.v1:type_name -> nockchain.public.v2.NockchainNoteV1 + 1, // 16: nockchain.public.v2.NockchainNoteV0.version:type_name -> nockchain.public.v2.Version + 13, // 17: nockchain.public.v2.NockchainNoteV0.name:type_name -> nockchain.public.v2.NockchainName + 18, // 18: nockchain.public.v2.NockchainNoteV0.lock:type_name -> nockchain.public.v2.NockchainLock + 19, // 19: nockchain.public.v2.NockchainNoteV0.source:type_name -> nockchain.public.v2.NockchainSource + 1, // 20: nockchain.public.v2.NockchainNoteV1.version:type_name -> nockchain.public.v2.Version + 13, // 21: nockchain.public.v2.NockchainNoteV1.name:type_name -> nockchain.public.v2.NockchainName + 18, // 22: nockchain.public.v2.NockchainNoteV1.note_data:type_name -> nockchain.public.v2.NockchainLock + 16, // 23: nockchain.public.v2.NockchainWitness.lmp:type_name -> nockchain.public.v2.NockchainLockMerkleProof + 14, // 24: nockchain.public.v2.NockchainWitness.pkh:type_name -> nockchain.public.v2.NockchainSignature + 18, // 25: nockchain.public.v2.NockchainLockMerkleProof.spend_condition:type_name -> nockchain.public.v2.NockchainLock + 19, // 26: nockchain.public.v2.NockchainSeed.output_source:type_name -> nockchain.public.v2.NockchainSource + 18, // 27: nockchain.public.v2.NockchainSeed.note_data:type_name -> nockchain.public.v2.NockchainLock + 28, // [28:28] is the sub-list for method output_type + 28, // [28:28] is the sub-list for method input_type + 28, // [28:28] is the sub-list for extension type_name + 28, // [28:28] is the sub-list for extension extendee + 0, // [0:28] is the sub-list for field type_name } func init() { file_types_proto_init() } @@ -1230,14 +1433,14 @@ func file_types_proto_init() { (*NockchainNote_V0)(nil), (*NockchainNote_V1)(nil), } - file_types_proto_msgTypes[13].OneofWrappers = []any{} + file_types_proto_msgTypes[15].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_types_proto_rawDesc), len(file_types_proto_rawDesc)), NumEnums: 2, - NumMessages: 16, + NumMessages: 18, NumExtensions: 0, NumServices: 0, }, diff --git a/proto/types.proto b/proto/types.proto index 23f00aa..de00881 100644 --- a/proto/types.proto +++ b/proto/types.proto @@ -46,11 +46,14 @@ message NockchainNamedSpend { message NockchainSpendV0 { repeated NockchainSignature signatures = 1; - repeated NockchainSeedV0 seeds = 2; + repeated NockchainSeed seeds = 2; uint64 fee = 3; } message NockchainSpendV1 { + repeated NockchainWitness witness = 1; + repeated NockchainSeed seeds = 2; + uint64 fee = 3; } message NockchainNote { @@ -63,12 +66,17 @@ message NockchainNoteV0 { Version version = 1; uint64 origin_page = 2; NockchainName name = 3; - NockchainLock lock = 5; - NockchainSource source = 6; - uint64 asset =7; + NockchainLock lock = 4; + NockchainSource source = 5; + uint64 asset =6; } message NockchainNoteV1 { + Version version = 1; + uint64 origin_page = 2; + NockchainName name = 3; + NockchainLock note_data = 4; + uint64 assets = 5; } message NockchainName { @@ -82,7 +90,18 @@ message NockchainSignature { repeated uint64 sig = 3; } -message NockchainSeedV0 { +message NockchainWitness { + NockchainLockMerkleProof lmp = 1; + repeated NockchainSignature pkh = 2; +} + +message NockchainLockMerkleProof { + NockchainLock spend_condition = 1; + uint64 axis = 2; + string merkle_root = 3; +} + +message NockchainSeed { optional NockchainSource output_source = 1; string lock_root = 2; NockchainLock note_data = 3; @@ -103,4 +122,4 @@ enum Version { V0 = 0; V1 = 1; V2 = 2; -} \ No newline at end of file +} diff --git a/wallet/nockhash.go b/wallet/nockhash.go index 85f2bba..8923f1e 100644 --- a/wallet/nockhash.go +++ b/wallet/nockhash.go @@ -9,7 +9,9 @@ import ( "github.com/phamminh0811/private-grpc/nockchain" ) -var LastName = [5]uint64{9541855607561054508, 12383849149342406623, 11220017934615522559, 678840671137489369, 8985908938884028381} +var ( + MerkleHash = [5]uint64{7971649669803894685, 16663670492333541326, 11038715785817710450, 18178011379925321681, 17049062282971338707} +) func HashPubkey(pkPoint crypto.CheetahPoint) [5]uint64 { belts := []crypto.Belt{{Value: 13}} @@ -22,7 +24,7 @@ func HashPubkey(pkPoint crypto.CheetahPoint) [5]uint64 { return crypto.Tip5HashBelts(belts) } -func HashSignature(signature *nockchain.NockchainSignature) ([5]uint64, error) { +func HashSignatureV0(signature *nockchain.NockchainSignature) ([5]uint64, error) { belts := []crypto.Belt{{Value: 16}} for _, i := range signature.Chal { belts = append(belts, crypto.Belt{Value: i}) @@ -43,6 +45,42 @@ func HashSignature(signature *nockchain.NockchainSignature) ([5]uint64, error) { return crypto.Tip5RehashTenCell(sigHash, crypto.Tip5ZeroZero), nil } +func HashSignatureV1(signature *nockchain.NockchainSignature) ([5]uint64, error) { + belts := []crypto.Belt{{Value: 16}} + for _, i := range signature.Chal { + belts = append(belts, crypto.Belt{Value: i}) + } + for _, i := range signature.Sig { + belts = append(belts, crypto.Belt{Value: i}) + } + for _, i := range crypto.MagicDyckForT8 { + belts = append(belts, crypto.Belt{Value: i}) + } + sigHash := crypto.Tip5HashBelts(belts) + pkPoint, err := crypto.CheetaPointFromBytes(base58.Decode(signature.Pubkey)) + if err != nil { + return [5]uint64{}, err + } + pkHash := HashPubkey(pkPoint) + sigHash = crypto.Tip5RehashTenCell(pkHash, sigHash) + pkHashSig := crypto.Tip5RehashTenCell(pkHash, sigHash) + return crypto.Tip5RehashTenCell(pkHashSig, crypto.Tip5ZeroZero), nil +} +func HashWitness(witness *nockchain.NockchainWitness) ([5]uint64, error) { + spendConditionHash := HashLock(witness.Lmp.SpendCondition) + rootHash := crypto.Base58ToTip5Hash(witness.Lmp.MerkleRoot) + rootHashZero := crypto.Tip5RehashTenCell(rootHash, crypto.Tip5Zero) + axisHashRoot := crypto.Tip5RehashTenCell(MerkleHash, rootHashZero) + lmpHash := crypto.Tip5RehashTenCell(spendConditionHash, axisHashRoot) + + sigHash, err := HashSignatureV1(witness.Pkh[0]) + if err != nil { + return [5]uint64{}, err + } + sigHashZeroZero := crypto.Tip5RehashTenCell(sigHash, crypto.Tip5ZeroZero) + return crypto.Tip5RehashTenCell(lmpHash, sigHashZeroZero), nil +} + func HashNoteData(lock *nockchain.NockchainLock) [5]uint64 { keysRequiredHash := crypto.Tip5HashLeaf(lock.KeysRequired) // TODO: handle multisig @@ -67,9 +105,10 @@ func HashOwner(pkPoint crypto.CheetahPoint) [5]uint64 { return crypto.Tip5RehashTenCell(crypto.Tip5One, pkHashedZeroZero) } -func NockName(ownerHash [5]uint64) ([5]uint64, [5]uint64) { - firstName := first(ownerHash) - return firstName, LastName +func NockFirstName(ownerHash [5]uint64) [5]uint64 { + ownerHashZero := crypto.Tip5RehashTenCell(ownerHash, crypto.Tip5Zero) + ownerHashZeroOne := crypto.Tip5RehashTenCell(crypto.Tip5One, ownerHashZero) + return crypto.Tip5RehashTenCell(crypto.Tip5Zero, ownerHashZeroOne) } func HashName(name *nockchain.NockchainName) [5]uint64 { @@ -117,6 +156,19 @@ func HashNoteV0(note *nockchain.NockchainNoteV0) ([5]uint64, error) { return crypto.Tip5RehashTenCell(p, q), nil } +func HashNoteV1(note *nockchain.NockchainNoteV1) [5]uint64 { + versionHash := crypto.Tip5HashBelts([]crypto.Belt{{Value: 1}, {Value: uint64(note.Version)}}) + blockHash := crypto.Tip5HashBelts([]crypto.Belt{{Value: 1}, {Value: note.OriginPage}}) + nameHash := HashName(note.Name) + noteDataHash := HashNoteData(note.NoteData) + assetHash := crypto.Tip5HashBelts([]crypto.Belt{{Value: 1}, {Value: uint64(note.Assets)}}) + + hashNoteDataAsset := crypto.Tip5RehashTenCell(noteDataHash, assetHash) + hashNameNoteDataAsset := crypto.Tip5RehashTenCell(nameHash, hashNoteDataAsset) + q := crypto.Tip5RehashTenCell(blockHash, hashNameNoteDataAsset) + return crypto.Tip5RehashTenCell(versionHash, q) +} + func HashTimelockIntent(timelock *nockchain.TimelockIntent) [5]uint64 { if timelock == nil { return crypto.Tip5Zero @@ -201,29 +253,26 @@ func HashSource(source *nockchain.NockchainSource) [5]uint64 { return crypto.Tip5RehashTenCell(sourceHash, crypto.Tip5One) } -func HashSeedWithoutSource(seed *nockchain.NockchainSeedV0) [5]uint64 { +func HashSeedWithoutSource(seed *nockchain.NockchainSeed) [5]uint64 { lockRoot := crypto.Base58ToTip5Hash(seed.LockRoot) - fmt.Println("lockRoot:", lockRoot) assetHash := crypto.Tip5HashBelts([]crypto.Belt{{Value: 1}, {Value: seed.Gift}}) parentHash := crypto.Base58ToTip5Hash(seed.ParentHash) - fmt.Println("parentHash:", parentHash) assetHashparentHash := crypto.Tip5RehashTenCell(assetHash, parentHash) noteDataHash := HashNoteData(seed.NoteData) - fmt.Println("noteDataHash:", noteDataHash) noteDataHashAssetParentHash := crypto.Tip5RehashTenCell(noteDataHash, assetHashparentHash) seedHash := crypto.Tip5RehashTenCell(lockRoot, noteDataHashAssetParentHash) return seedHash } -func HashSeed(seed *nockchain.NockchainSeedV0) [5]uint64 { +func HashSeed(seed *nockchain.NockchainSeed) [5]uint64 { seedHash := HashSeedWithoutSource(seed) sourceHash := HashSource(seed.OutputSource) return crypto.Tip5RehashTenCell(sourceHash, seedHash) } -func HashSeedVarLen(seed *nockchain.NockchainSeedV0) [5]uint64 { +func HashSeedVarLen(seed *nockchain.NockchainSeed) [5]uint64 { belts := []crypto.Belt{{Value: 0}} lockRoot := crypto.Base58ToTip5Hash(seed.LockRoot) for _, i := range lockRoot { @@ -284,85 +333,91 @@ func HashSpendV0(spend *nockchain.NockchainSpendV0) ([5]uint64, error) { return [5]uint64{}, fmt.Errorf("signatures can not be empty") } - sigHash, err := HashSignature(spend.Signatures[0]) + sigHash, err := HashSignatureV0(spend.Signatures[0]) if err != nil { return [5]uint64{}, err } - seedsTree := NewZTree( - func(i interface{}) [5]uint64 { - if seed, ok := i.(*nockchain.NockchainSeedV0); ok { - return HashSeedVarLen(seed) - } else { - return [5]uint64{} - } - }, - func(i interface{}) ([5]uint64, error) { - if seed, ok := i.(*nockchain.NockchainSeedV0); ok { - return HashSeedWithoutSource(seed), nil - } else { - return [5]uint64{}, fmt.Errorf("invalid input type") - } - }, - ) - for _, seed := range spend.Seeds { - seedsTree.Insert(seed, seed) - } - finalSeedHash, err := seedsTree.Hash() + seedHashFee, err := HashSeedsAndFee(spend.Seeds, spend.Fee) if err != nil { return [5]uint64{}, err } - - feeHash := crypto.Tip5HashBelts([]crypto.Belt{{Value: 1}, {Value: spend.Fee}}) - seedHashFee := crypto.Tip5RehashTenCell(finalSeedHash, feeHash) return crypto.Tip5RehashTenCell(sigHash, seedHashFee), nil - } -func HashMsg(spend *nockchain.NockchainSpendV0) ([5]uint64, error) { +func HashSpendV1(spend *nockchain.NockchainSpendV1) ([5]uint64, error) { + if len(spend.Witness) == 0 { + return [5]uint64{}, fmt.Errorf("witness can not be empty") + } + + witnessHash, err := HashWitness(spend.Witness[0]) + if err != nil { + return [5]uint64{}, err + } + + seedHashFee, err := HashSeedsAndFee(spend.Seeds, spend.Fee) + if err != nil { + return [5]uint64{}, err + } + return crypto.Tip5RehashTenCell(witnessHash, seedHashFee), nil +} + +func HashMsg(seeds []*nockchain.NockchainSeed, fee uint64) ([5]uint64, error) { seedsTree := NewZTree( func(i interface{}) [5]uint64 { - if seed, ok := i.(*nockchain.NockchainSeedV0); ok { + if seed, ok := i.(*nockchain.NockchainSeed); ok { return HashSeedVarLen(seed) } else { return [5]uint64{} } }, func(i interface{}) ([5]uint64, error) { - if seed, ok := i.(*nockchain.NockchainSeedV0); ok { + if seed, ok := i.(*nockchain.NockchainSeed); ok { return HashSeed(seed), nil } else { return [5]uint64{}, fmt.Errorf("invalid input type") } }, ) - for _, seed := range spend.Seeds { + for _, seed := range seeds { seedsTree.Insert(seed, seed) } finalSeedHash, err := seedsTree.Hash() if err != nil { return [5]uint64{}, err } - feeHash := crypto.Tip5HashBelts([]crypto.Belt{{Value: 1}, {Value: spend.Fee}}) + feeHash := crypto.Tip5HashBelts([]crypto.Belt{{Value: 1}, {Value: fee}}) return crypto.Tip5RehashTenCell(finalSeedHash, feeHash), nil } -// func HashInput(input *nockchain.NockchainInput) ([5]uint64, error) { -// nameHash := HashName(input.Name) +func HashSeedsAndFee(seeds []*nockchain.NockchainSeed, fee uint64) ([5]uint64, error) { + seedsTree := NewZTree( + func(i interface{}) [5]uint64 { + if seed, ok := i.(*nockchain.NockchainSeed); ok { + return HashSeedVarLen(seed) + } else { + return [5]uint64{} + } + }, + func(i interface{}) ([5]uint64, error) { + if seed, ok := i.(*nockchain.NockchainSeed); ok { + return HashSeedWithoutSource(seed), nil + } else { + return [5]uint64{}, fmt.Errorf("invalid input type") + } + }, + ) + for _, seed := range seeds { + seedsTree.Insert(seed, seed) + } + finalSeedHash, err := seedsTree.Hash() + if err != nil { + return [5]uint64{}, err + } -// noteHash, err := HashNote(input.Note) -// if err != nil { -// return [5]uint64{}, err - -// } -// spendHash, err := HashSpend(input.Spend) -// if err != nil { -// return [5]uint64{}, err - -// } -// hashNoteSpend := crypto.Tip5RehashTenCell(noteHash, spendHash) -// return crypto.Tip5RehashTenCell(nameHash, hashNoteSpend), nil -// } + feeHash := crypto.Tip5HashBelts([]crypto.Belt{{Value: 1}, {Value: fee}}) + return crypto.Tip5RehashTenCell(finalSeedHash, feeHash), nil +} func ComputeTxId(spends []*nockchain.NockchainNamedSpend, version uint64) ([5]uint64, error) { var spendHash [5]uint64 @@ -399,7 +454,37 @@ func ComputeTxId(spends []*nockchain.NockchainNamedSpend, version uint64) ([5]ui if err != nil { return [5]uint64{}, fmt.Errorf("error hashing spends: %v", err) } - fmt.Println("spendHash:", spendHash) + case 1: + spendTree := NewZTree( + func(i interface{}) [5]uint64 { + if name, ok := i.(*nockchain.NockchainName); ok { + return HashNameVarLen(name) + } else { + return [5]uint64{} + } + }, + func(i interface{}) ([5]uint64, error) { + if spendEntry, ok := i.(*nockchain.NockchainNamedSpend); ok { + nameHash := HashName(spendEntry.Name) + spendV1Hash, err := HashSpendV1(spendEntry.GetWitness()) + if err != nil { + return [5]uint64{}, fmt.Errorf("error hashing spend: %v", err) + } + + oneHashspend := crypto.Tip5RehashTenCell(crypto.Tip5One, spendV1Hash) + return crypto.Tip5RehashTenCell(nameHash, oneHashspend), nil + } else { + return [5]uint64{}, fmt.Errorf("invalid input type") + } + }, + ) + for _, spend := range spends { + spendTree.Insert(spend.Name, spend) + } + spendHash, err = spendTree.Hash() + if err != nil { + return [5]uint64{}, fmt.Errorf("error hashing spends: %v", err) + } default: return [5]uint64{}, fmt.Errorf("unsupported version %d", version) } @@ -431,9 +516,3 @@ func ComputeSig(m crypto.MasterKey, msg [5]uint64) ([8]uint64, [8]uint64, error) sigT8 := crypto.BigIntToT8(*sig) return chalT8, sigT8, nil } - -func first(ownerHash [5]uint64) [5]uint64 { - ownerHashZero := crypto.Tip5RehashTenCell(ownerHash, crypto.Tip5Zero) - ownerHashZeroOne := crypto.Tip5RehashTenCell(crypto.Tip5One, ownerHashZero) - return crypto.Tip5RehashTenCell(crypto.Tip5Zero, ownerHashZeroOne) -} diff --git a/wallet/noun.go b/wallet/noun.go index b2ddfca..fc4381c 100644 --- a/wallet/noun.go +++ b/wallet/noun.go @@ -24,6 +24,16 @@ func Uint64ToBitsLSB0(x uint64) []bool { return bits } +func BitsToUint64LSB0(bits []bool, start, sz int) uint64 { + var data uint64 + for i := 0; i < sz; i++ { + if bits[start+i] { + data |= 1 << i // LSB0: bit 0 = least significant + } + } + return data +} + func BoolsToBytesLSB0(bits []bool) []byte { if len(bits) == 0 { return nil @@ -40,6 +50,16 @@ func BoolsToBytesLSB0(bits []bool) []byte { return bytes } +func BytesToBoolsLSB0(data []byte) []bool { + bools := make([]bool, len(data)*8) + for i, b := range data { + for j := 0; j < 8; j++ { + bools[i*8+j] = ((b >> j) & 1) == 1 // LSB0 order + } + } + return bools +} + func EncodeNoteData(noteData *nockchain.NockchainLock) []byte { bits := InitialBits @@ -89,3 +109,48 @@ func EncodeNoteData(noteData *nockchain.NockchainLock) []byte { bits = append(bits, []bool{true, false, false, true, false, true, false, true}...) return BoolsToBytesLSB0(bits) } + +func DecodeNoteData(blob []byte) *nockchain.NockchainLock { + bits := BytesToBoolsLSB0(blob) + + start := len(InitialBits) + 1 + count := 0 + for !bits[start] { + start += 1 + count += 1 + } + start += 1 + numPksSz := uint64(1) + if count > 1 { + numPksSz = BitsToUint64LSB0(bits, start, count-1) + start += count - 1 + } + numPks := BitsToUint64LSB0(bits, start, int(numPksSz)) + start += int(numPksSz) + start += 2 + pkHash := [5]uint64{} + for i := 0; i < 5; i++ { + if i != 4 { + start += 2 + } + start += 1 + count := 0 + for !bits[start] { + start += 1 + count += 1 + } + start += 1 + hashSz := uint64(count) + if count > 1 { + hashSz = BitsToUint64LSB0(bits, start, count-1) + 1<<(count-1) + start += count - 1 + } + hash := BitsToUint64LSB0(bits, start, int(hashSz)) + start += int(hashSz) + pkHash[i] = hash + } + return &nockchain.NockchainLock{ + KeysRequired: numPks, + Pubkeys: []string{crypto.Tip5HashToBase58(pkHash)}, + } +} diff --git a/wallet/service.go b/wallet/service.go index f8ccab8..1b22543 100644 --- a/wallet/service.go +++ b/wallet/service.go @@ -17,7 +17,7 @@ import ( ) const ( - WitnessWordsCount = 0 + WitnessWordsCount = 55 SignatureWordsCount = 31 SeedWordsCount = 13 BaseFee = 1 << 15 @@ -390,32 +390,6 @@ func (h *GprcHandler) CreateTx(ctx context.Context, req *nockchain.CreateTxReque } } - nameTree := NewZTree( - func(i interface{}) [5]uint64 { - if name, ok := i.(*nockchain.NockchainName); ok { - return HashNameVarLen(name) - } else { - return [5]uint64{} - } - }, - nil, - ) - for _, name := range nnames { - nameTree.Insert(name, nil) - } - nameList := nameTree.Tap() - nameKeys := []string{} - for _, nameKey := range nameList { - name := nameKey.Key.(*nockchain.NockchainName) - nameKeys = append(nameKeys, name.First+" "+name.Last) - } - indices := make([]int, len(nnames)) - for i, name := range nnames { - if idx := slices.Index(nameKeys, name.First+" "+name.Last); idx != -1 { - indices[idx] = i - } - } - specs := strings.Split(req.Recipients, ",") if len(specs) == 0 { return nil, fmt.Errorf("at least one output must be provided") @@ -454,6 +428,11 @@ func (h *GprcHandler) CreateTx(ctx context.Context, req *nockchain.CreateTxReque } masterKey = &childKey } + masterPkPoint, err := crypto.CheetaPointFromBytes(masterKey.PublicKey) + if err != nil { + return nil, err + } + pkHash := HashPubkey(masterPkPoint) recipent := &nockchain.NockchainLock{ KeysRequired: 1, @@ -464,23 +443,40 @@ func (h *GprcHandler) CreateTx(ctx context.Context, req *nockchain.CreateTxReque } refundAddr := req.RefundAddress if refundAddr == "" { - masterPkPoint, err := crypto.CheetaPointFromBytes(masterKey.PublicKey) - if err != nil { - return nil, err - } - pkHash := HashPubkey(masterPkPoint) refundAddr = crypto.Tip5HashToBase58(pkHash) } refundLock := &nockchain.NockchainLock{ KeysRequired: 1, Pubkeys: []string{refundAddr}, } + + ownerLock := &nockchain.NockchainLock{ + KeysRequired: 1, + Pubkeys: []string{crypto.Tip5HashToBase58(pkHash)}, + } + + ownerHash := HashLock(ownerLock) // Scan key to get notes - masterKeyScan, err := h.client.WalletGetBalance(&nockchain.GetBalanceRequest{ - Selector: &nockchain.GetBalanceRequest_Address{ - Address: base58.Encode(masterKey.PublicKey), - }, - }) + var scanReq *nockchain.GetBalanceRequest + switch req.Version { + case 0: + scanReq = &nockchain.GetBalanceRequest{ + Selector: &nockchain.GetBalanceRequest_Address{ + Address: base58.Encode(masterKey.PublicKey), + }, + } + case 1: + + firstName := crypto.Tip5RehashTenCell(crypto.Tip5Zero, ownerHash) + scanReq = &nockchain.GetBalanceRequest{ + Selector: &nockchain.GetBalanceRequest_FirstName{ + FirstName: crypto.Tip5HashToBase58(firstName), + }, + } + default: + return nil, fmt.Errorf("unsuport version") + } + masterKeyScan, err := h.client.WalletGetBalance(scanReq) if err != nil { return nil, err } @@ -535,23 +531,30 @@ func (h *GprcHandler) CreateTx(ctx context.Context, req *nockchain.CreateTxReque notesV1Sort := make([]*nockchain.NockchainNoteV1, len(notesV1)) copy(notesV1Sort, notesV1) - slices.SortFunc(notesV0Sort, func(note1, note2 *nockchain.NockchainNoteV0) int { - return cmp.Compare(note1.Asset, note2.Asset) - }) - // TODO: sort V1 notes + switch req.Version { + case 0: + slices.SortFunc(notesV0Sort, func(note1, note2 *nockchain.NockchainNoteV0) int { + return cmp.Compare(note2.Asset, note1.Asset) + }) + case 1: + slices.SortFunc(notesV1Sort, func(note1, note2 *nockchain.NockchainNoteV1) int { + return cmp.Compare(note2.Assets, note1.Assets) + }) + } spends := []*nockchain.NockchainNamedSpend{} - for _, note := range notesV0Sort { - giftPortion := uint64(0) - feePortion := uint64(0) - if giftRemaining != 0 { - giftPortion = min(giftRemaining, note.Asset) + for i := 0; i < len(names); i++ { + asset := uint64(0) + switch req.Version { + case 0: + asset = notesV0Sort[i].Asset + case 1: + asset = notesV1Sort[i].Assets } + giftPortion := min(giftRemaining, asset) - feeAvailable := note.Asset - giftPortion - if feeRemaining != 0 { - feePortion = min(feeRemaining, feeAvailable) - } + feeAvailable := asset - giftPortion + feePortion := min(feeRemaining, feeAvailable) if giftPortion == 0 && feePortion == 0 { continue @@ -560,81 +563,126 @@ func (h *GprcHandler) CreateTx(ctx context.Context, req *nockchain.CreateTxReque giftRemaining = giftRemaining - giftPortion feeRemaining = feeRemaining - feePortion - refund := note.Asset - giftPortion - feePortion + refund := asset - giftPortion - feePortion if refund == 0 && giftPortion == 0 { continue } - parentHash, err := HashNoteV0(note) + var parentHash [5]uint64 + switch req.Version { + case 0: + parentHash, err = HashNoteV0(notesV0Sort[i]) + if err != nil { + return nil, err + } + case 1: + parentHash = HashNoteV1(notesV1Sort[i]) + } + lockRoot := HashLock(recipent) + seeds := []*nockchain.NockchainSeed{} + if giftPortion != 0 { + wordCount += SeedWordsCount + seeds = append(seeds, &nockchain.NockchainSeed{ + OutputSource: nil, + LockRoot: crypto.Tip5HashToBase58(lockRoot), + NoteData: recipent, + Gift: giftPortion, + ParentHash: crypto.Tip5HashToBase58(parentHash), + }) + } + + if refund != 0 { + wordCount += SeedWordsCount + lockRoot := HashLock(refundLock) + seeds = append(seeds, &nockchain.NockchainSeed{ + OutputSource: nil, + LockRoot: crypto.Tip5HashToBase58(lockRoot), + NoteData: refundLock, + Gift: refund, + ParentHash: crypto.Tip5HashToBase58(parentHash), + }) + } + + msg, err := HashMsg(seeds, feePortion) if err != nil { return nil, err } - if req.Version == 0 { - lockRoot := HashLock(recipent) - wordCount += SeedWordsCount - seeds := []*nockchain.NockchainSeedV0{ - { - OutputSource: nil, - LockRoot: crypto.Tip5HashToBase58(lockRoot), - NoteData: recipent, - Gift: giftPortion, - ParentHash: crypto.Tip5HashToBase58(parentHash), - }, - } - if refund != 0 { - wordCount += SeedWordsCount - lockRoot := HashLock(refundLock) - seeds = append(seeds, &nockchain.NockchainSeedV0{ - OutputSource: nil, - LockRoot: crypto.Tip5HashToBase58(lockRoot), - NoteData: refundLock, - Gift: refund, - ParentHash: crypto.Tip5HashToBase58(parentHash), - }) - } + // sign + chalT8, sigT8, err := ComputeSig(*masterKey, msg) + if err != nil { + return nil, err + } + sigs := []*nockchain.NockchainSignature{ + { + Pubkey: base58.Encode(masterKey.PublicKey), + Chal: chalT8[:], + Sig: sigT8[:], + }, + } + switch req.Version { + case 0: spend := nockchain.NockchainSpendV0{ Signatures: nil, Seeds: seeds, Fee: feePortion, } - msg, err := HashMsg(&spend) - if err != nil { - return nil, err - } - fmt.Println("msg:", msg) - - // sign - chalT8, sigT8, err := ComputeSig(*masterKey, msg) - if err != nil { - return nil, err - } - - masterPkPoint, err := crypto.CheetaPointFromBytes(masterKey.PublicKey) - if err != nil { - return nil, err - } - fmt.Println("master pk point:", masterPkPoint) - spend.Signatures = []*nockchain.NockchainSignature{ - { - Pubkey: base58.Encode(masterKey.PublicKey), - Chal: chalT8[:], - Sig: sigT8[:], - }, - } + spend.Signatures = sigs wordCount += SignatureWordsCount + nameIdx := -1 + for j := 0; j < len(names); j++ { + if nnames[j].Last == notesV0Sort[i].Name.Last { + nameIdx = j + break + } + } spends = append(spends, &nockchain.NockchainNamedSpend{ - Name: note.Name, + Name: nnames[nameIdx], SpendKind: &nockchain.NockchainNamedSpend_Legacy{ Legacy: &spend, }, }) + case 1: + spend := nockchain.NockchainSpendV1{ + Witness: nil, + Seeds: seeds, + Fee: feePortion, + } + + spend.Witness = []*nockchain.NockchainWitness{ + { + Lmp: &nockchain.NockchainLockMerkleProof{ + SpendCondition: ownerLock, + Axis: 1, + MerkleRoot: crypto.Tip5HashToBase58(ownerHash), + }, + Pkh: sigs, + }, + } + wordCount += WitnessWordsCount + nameIdx := -1 + for j := 0; j < len(names); j++ { + if nnames[j].Last == notesV1Sort[i].Name.Last { + nameIdx = j + break + } + } + spends = append(spends, &nockchain.NockchainNamedSpend{ + Name: nnames[nameIdx], + SpendKind: &nockchain.NockchainNamedSpend_Witness{ + Witness: &spend, + }, + }) } } + if giftRemaining != 0 || feeRemaining != 0 { + return nil, fmt.Errorf("insufficient funds to pay fee and gift") + } + if wordCount*BaseFee > int(req.Fee) { return nil, fmt.Errorf("min fee not met, this transaction requires at least: %d", wordCount*BaseFee) } diff --git a/wallet/service_test.go b/wallet/service_test.go index 13aab26..44ac480 100644 --- a/wallet/service_test.go +++ b/wallet/service_test.go @@ -435,7 +435,7 @@ func TestScan(t *testing.T) { assert.NotEmpty(t, masterKey1Scan.Notes) } -func TestEncodeNoun(t *testing.T) { +func TestEncodeDecodeNoun(t *testing.T) { lock1 := &nockchain.NockchainLock{ KeysRequired: 1, Pubkeys: []string{"5wef35rKxbJDJRAtzGG1VwbMQK9jF3Wr5e6fyMsh2hxnCQZDZJV1YNQ"}, @@ -444,143 +444,22 @@ func TestEncodeNoun(t *testing.T) { lock1Encode := wallet.EncodeNoteData(lock1) assert.Equal(t, lock1Encode, []byte{89, 192, 131, 91, 67, 199, 5, 16, 32, 24, 199, 52, 39, 171, 121, 6, 15, 240, 167, 243, 69, 34, 254, 110, 4, 78, 3, 8, 44, 245, 128, 176, 69, 72, 150, 253, 6, 232, 167, 34, 133, 115, 154, 56, 106, 106, 192, 175, 176, 88, 89, 160, 208, 117, 55, 78, 5}) + lock1Decode := wallet.DecodeNoteData(lock1Encode) + assert.Equal(t, lock1Decode.KeysRequired, lock1.KeysRequired) + assert.Equal(t, lock1Decode.Pubkeys, lock1.Pubkeys) + lock2 := lock1 lock2.Pubkeys = []string{"7UXNF2HXzEaPUvLDVDgGJyriKqpd2974Kj7U2RnLuBPeRGa7ZezhGmK"} lock2Encode := wallet.EncodeNoteData(lock2) assert.Equal(t, lock2Encode, []byte{89, 192, 131, 91, 67, 199, 5, 248, 151, 186, 241, 213, 183, 156, 103, 181, 1, 254, 206, 33, 184, 3, 142, 3, 99, 101, 0, 1, 246, 168, 22, 252, 21, 155, 53, 205, 0, 2, 172, 67, 150, 149, 228, 139, 80, 206, 0, 1, 203, 54, 188, 141, 54, 21, 39, 193, 84}) + + lock2Decode := wallet.DecodeNoteData(lock2Encode) + assert.Equal(t, lock2Decode.KeysRequired, lock2.KeysRequired) + assert.Equal(t, lock2Decode.Pubkeys, lock2.Pubkeys) } -// // This test should be run with timeout 120s -// func TestFullFlow(t *testing.T) { -// mnemonic1 := "rail nurse smile angle uphold gun kitten spoon quick frozen trigger cable decorate episode blame tray off bag arena taxi approve breeze job letter" -// masterKey1, err := crypto.MasterKeyFromSeed(mnemonic1) -// assert.NoError(t, err) -// fmt.Println(base58.Encode(masterKey1.PublicKey)) -// mnemonic2 := "brass vacuum stairs hurt brisk govern describe enforce fly exact rescue capable belt flavor lottery sauce easy frame orange legal injury border obey novel" -// masterKey2, err := crypto.MasterKeyFromSeed(mnemonic2) -// assert.NoError(t, err) - -// inputName := "[4taoqkpysafnp64WBQyzHDKVrqkMeNrdAiVSbWdzZmj7yQYZgQtCq4W 9cjUFbdtaFHeXNWCAKjsTphBchHmCoUU6a1aDbJAFz9qHqeG8osh4wF]" - -// nc, err := wallet.NewNockchainClient("nockchain-api.zorp.io:443") -// assert.NoError(t, err) - -// masterKeyScan, err := nc.WalletGetBalance(base58.Encode(masterKey1.PublicKey)) -// assert.NoError(t, err) - -// var note *nockchain.NockchainNote -// for _, balanceEntry := range masterKeyScan.Notes { -// firstName := crypto.Tip5HashToBase58([5]uint64{ -// balanceEntry.Name.First.Belt_1.Value, -// balanceEntry.Name.First.Belt_2.Value, -// balanceEntry.Name.First.Belt_3.Value, -// balanceEntry.Name.First.Belt_4.Value, -// balanceEntry.Name.First.Belt_5.Value, -// }) -// lastName := crypto.Tip5HashToBase58([5]uint64{ -// balanceEntry.Name.Last.Belt_1.Value, -// balanceEntry.Name.Last.Belt_2.Value, -// balanceEntry.Name.Last.Belt_3.Value, -// balanceEntry.Name.Last.Belt_4.Value, -// balanceEntry.Name.Last.Belt_5.Value, -// }) -// nname := "[" + firstName + " " + lastName + "]" -// if nname == inputName { -// nnote := wallet.ParseBalanceEntry(balanceEntry) -// note = &nnote -// break -// } -// } -// assert.NotNil(t, note) - -// parentHash, err := wallet.HashNote(note) -// assert.NoError(t, err) - -// gift := uint64(100000) -// seed1 := &nockchain.NockchainSeed{ -// OutputSource: nil, -// Recipient: &nockchain.NockchainLock{ -// KeysRequired: 1, -// Pubkeys: []string{base58.Encode(masterKey2.PublicKey)}, -// }, -// TimelockIntent: nil, -// Gift: gift, -// ParentHash: crypto.Tip5HashToBase58(parentHash), -// } - -// gift = note.Asset - gift - 100 -// seed2 := &nockchain.NockchainSeed{ -// OutputSource: nil, -// Recipient: &nockchain.NockchainLock{ -// KeysRequired: 1, -// Pubkeys: []string{base58.Encode(masterKey1.PublicKey)}, -// }, -// TimelockIntent: nil, -// Gift: gift, -// ParentHash: crypto.Tip5HashToBase58(parentHash), -// } - -// spend := nockchain.NockchainSpend{ -// Signatures: nil, -// Seeds: []*nockchain.NockchainSeed{ -// seed1, seed2, -// }, -// Fee: 100, -// } - -// msg, err := wallet.HashMsg(&spend) -// assert.NoError(t, err) - -// chalT8, sigT8, err := wallet.ComputeSig(*masterKey1, msg) -// assert.NoError(t, err) - -// spend.Signatures = []*nockchain.NockchainSignature{ -// { -// Pubkey: base58.Encode(masterKey1.PublicKey), -// Chal: chalT8[:], -// Sig: sigT8[:], -// }, -// } - -// input := nockchain.NockchainInput{ -// Name: &nockchain.NockchainName{ -// First: "4taoqkpysafnp64WBQyzHDKVrqkMeNrdAiVSbWdzZmj7yQYZgQtCq4W", -// Last: "9cjUFbdtaFHeXNWCAKjsTphBchHmCoUU6a1aDbJAFz9qHqeG8osh4wF", -// }, -// Note: note, -// Spend: &spend, -// } - -// id, err := wallet.ComputeTxId([]*nockchain.NockchainInput{&input}, &nockchain.TimelockRange{ -// Min: nil, -// Max: nil, -// }, 100) -// assert.NoError(t, err) - -// rawTx := nockchain.RawTx{ -// TxId: crypto.Tip5HashToBase58(id), -// Inputs: []*nockchain.NockchainInput{&input}, -// TimelockRange: &nockchain.TimelockRange{ -// Min: nil, -// Max: nil, -// }, -// TotalFees: 100, -// } -// resp, err := nc.WalletSendTransaction(&rawTx) -// assert.NoError(t, err) -// assert.Equal(t, resp.Result, &nockchain.WalletSendTransactionResponse_Ack{ -// Ack: &nockchain.Acknowledged{}, -// }) - -// txAcceptedResp, err := nc.TxAccepted(rawTx.TxId) -// assert.NoError(t, err) -// assert.Equal(t, txAcceptedResp.Result, &nockchain.TransactionAcceptedResponse_Accepted{ -// Accepted: true, -// }) -// } - // This test should be run with timeout 300s -func TestCreateTx(t *testing.T) { +func TestCreateTxV0(t *testing.T) { seed1 := "rail nurse smile angle uphold gun kitten spoon quick frozen trigger cable decorate episode blame tray off bag arena taxi approve breeze job letter" masterKey1, err := crypto.MasterKeyFromSeed(seed1) assert.NoError(t, err) @@ -626,3 +505,34 @@ func TestCreateTx(t *testing.T) { // the result is taken from create-tx scripts assert.Equal(t, res.RawTx.TxId, "6oDgTWnk6sL98yK49hcQTRAfCdFfgKh58U6TDTKmGPirhoxkMii2D6E") } + +func TestCreateTxV1(t *testing.T) { + seed := "pledge vessel toilet sunny hockey skirt spend wire disorder attitude crumble lecture problem bundle bone rather address over suit ancient primary gospel silent repair" + _, err := crypto.MasterKeyFromSeed(seed) + assert.NoError(t, err) + + nc, err := wallet.NewNockchainClient("nockchain-api.zorp.io:443") + assert.NoError(t, err) + handler := wallet.NewGprcHandler(*nc) + + inputs := "[9McQZWZzqFCLwsF37gUHLHt3cxWVbLA64SabN3AMMSgNuSM2b9SmMsj 88uZqY63kneffJnVAEYpb67W96sk3xsdFYTjzj8DSBiigL9AZd9pHsC],[9McQZWZzqFCLwsF37gUHLHt3cxWVbLA64SabN3AMMSgNuSM2b9SmMsj 2JLcp5oXBTtgTCKN4na9Dnk2YuHJCXuDAkk5qT8n9iP1d4iz1c3Amfo]" + + recipients := fmt.Sprintf("%s:747336", "D6NUb9HC4ursvZzYdMAtWAMSyyJWwtdqnsyRXxsADsyqQwh5dcDsTRm") + fee := 4456448 + + req := &nockchain.CreateTxRequest{ + Names: inputs, + Recipients: recipients, + Fee: uint64(fee), + IsMasterKey: true, + Seed: seed, + Index: 0, + Hardened: false, + Version: 1, + } + + resp, err := handler.CreateTx(context.Background(), req) + assert.NoError(t, err) + + assert.Equal(t, resp.RawTx.TxId, "9DfaTJgtsGU8Fs2mAPhGpiMMw5RcRXjEVfnmFcTTxoSFMLWpv2Ae4eG") +} diff --git a/wallet/types.go b/wallet/types.go index f542f1e..ba32f62 100644 --- a/wallet/types.go +++ b/wallet/types.go @@ -1,14 +1,28 @@ package wallet import ( - "fmt" - "github.com/btcsuite/btcd/btcutil/base58" "github.com/phamminh0811/private-grpc/crypto" "github.com/phamminh0811/private-grpc/nockchain" ) func ParseBalanceEntry(entry *nockchain.BalanceEntry) nockchain.NockchainNote { + name := &nockchain.NockchainName{ + First: crypto.Tip5HashToBase58([5]uint64{ + entry.Name.First.Belt_1.Value, + entry.Name.First.Belt_2.Value, + entry.Name.First.Belt_3.Value, + entry.Name.First.Belt_4.Value, + entry.Name.First.Belt_5.Value, + }), + Last: crypto.Tip5HashToBase58([5]uint64{ + entry.Name.Last.Belt_1.Value, + entry.Name.Last.Belt_2.Value, + entry.Name.Last.Belt_3.Value, + entry.Name.Last.Belt_4.Value, + entry.Name.Last.Belt_5.Value, + }), + } switch entry.Note.NoteVersion.(type) { case *nockchain.Note_Legacy: note := entry.Note.GetLegacy() @@ -50,22 +64,7 @@ func ParseBalanceEntry(entry *nockchain.BalanceEntry) nockchain.NockchainNote { V0: &nockchain.NockchainNoteV0{ Version: version, OriginPage: note.OriginPage.Value, - Name: &nockchain.NockchainName{ - First: crypto.Tip5HashToBase58([5]uint64{ - entry.Name.First.Belt_1.Value, - entry.Name.First.Belt_2.Value, - entry.Name.First.Belt_3.Value, - entry.Name.First.Belt_4.Value, - entry.Name.First.Belt_5.Value, - }), - Last: crypto.Tip5HashToBase58([5]uint64{ - entry.Name.Last.Belt_1.Value, - entry.Name.Last.Belt_2.Value, - entry.Name.Last.Belt_3.Value, - entry.Name.Last.Belt_4.Value, - entry.Name.Last.Belt_5.Value, - }), - }, + Name: name, Lock: &nockchain.NockchainLock{ KeysRequired: uint64(note.Lock.KeysRequired), Pubkeys: pubkeys, @@ -79,11 +78,22 @@ func ParseBalanceEntry(entry *nockchain.BalanceEntry) nockchain.NockchainNote { }, } case *nockchain.Note_V1: - fmt.Println("go here???") - // Handle V1 notes if needed + note := entry.Note.GetV1() + version := nockchain.Version(note.Version.Value) + if len(note.NoteData.Entries) != 1 || note.NoteData.Entries[0].Key != "lock" { + panic("invalid note data") + } + lock := DecodeNoteData(note.NoteData.Entries[0].Blob) + return nockchain.NockchainNote{ Note: &nockchain.NockchainNote_V1{ - V1: &nockchain.NockchainNoteV1{}, + V1: &nockchain.NockchainNoteV1{ + Version: version, + OriginPage: note.OriginPage.Value, + Name: name, + NoteData: lock, + Assets: note.Assets.Value, + }, }, } default: @@ -134,7 +144,91 @@ func ConvertNamedSpend(spend *nockchain.NockchainNamedSpend) (*nockchain.SpendEn }, } case *nockchain.NockchainNamedSpend_Witness: - // TODO: handle v1 + witnessSpend := spend.GetWitness() + seeds := []*nockchain.Seed{} + for _, seed := range witnessSpend.Seeds { + seeds = append(seeds, &nockchain.Seed{ + OutputSource: &nockchain.Source{ + Hash: ParseHash(seed.OutputSource.Source), + Coinbase: seed.OutputSource.IsCoinbase, + }, + LockRoot: ParseHash(seed.LockRoot), + NoteData: &nockchain.NoteData{ + Entries: []*nockchain.NoteDataEntry{ + { + Key: "lock", + Blob: EncodeNoteData(seed.NoteData), + }, + }, + }, + Gift: &nockchain.Nicks{ + Value: seed.Gift, + }, + ParentHash: ParseHash(seed.ParentHash), + }) + } + + witness := witnessSpend.Witness[0] + lockHashes := []*nockchain.Hash{} + for i := 0; i < len(witness.Lmp.SpendCondition.Pubkeys); i++ { + lockHashes = append(lockHashes, ParseHash(witness.Lmp.SpendCondition.Pubkeys[i])) + } + + pkhSigs := []*nockchain.PkhSignatureEntry{} + for _, pkh := range witness.Pkh { + pk, err := crypto.CheetaPointFromBytes(base58.Decode(pkh.Pubkey)) + if err != nil { + return nil, err + } + schnorrPk, err := ParseSchnorrPubkey(pkh.Pubkey) + if err != nil { + return nil, err + } + + pkHash := HashPubkey(pk) + pkhSigs = append(pkhSigs, &nockchain.PkhSignatureEntry{ + Hash: ParseHash(crypto.Tip5HashToBase58(pkHash)), + Pubkey: schnorrPk, + Signature: &nockchain.SchnorrSignature{ + Chal: ParseEightBelt(pkh.Chal), + Sig: ParseEightBelt(pkh.Sig), + }, + }) + } + convertedSpend = &nockchain.Spend{ + SpendKind: &nockchain.Spend_Witness{ + Witness: &nockchain.WitnessSpend{ + Witness: &nockchain.Witness{ + LockMerkleProof: &nockchain.LockMerkleProof{ + SpendCondition: &nockchain.SpendCondition{ + Primitives: []*nockchain.LockPrimitive{ + { + Primitive: &nockchain.LockPrimitive_Pkh{ + Pkh: &nockchain.PkhLock{ + M: witness.Lmp.SpendCondition.KeysRequired, + Hashes: lockHashes, + }, + }, + }, + }, + }, + Axis: witness.Lmp.Axis, + Proof: &nockchain.MerkleProof{ + Root: ParseHash(witness.Lmp.MerkleRoot), + Path: []*nockchain.Hash{}, + }, + }, + PkhSignature: &nockchain.PkhSignature{ + Entries: pkhSigs, + }, + }, + Seeds: seeds, + Fee: &nockchain.Nicks{ + Value: witnessSpend.Fee, + }, + }, + }, + } } // Conversion logic here return &nockchain.SpendEntry{