From 6c2867c11cbe47753fc1e371429178a6d9ccfbec Mon Sep 17 00:00:00 2001 From: Calvin Kim Date: Wed, 23 Oct 2024 16:32:38 +0900 Subject: [PATCH 1/3] wire: add Copy() to LeafData --- wire/leaf.go | 17 +++++++++++++++++ wire/leaf_test.go | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/wire/leaf.go b/wire/leaf.go index a068bf8a..b61044be 100644 --- a/wire/leaf.go +++ b/wire/leaf.go @@ -49,6 +49,23 @@ type LeafData struct { PkScript []byte } +// Copy creates a deep copy of the leafdata so the original does not get modified +// when the copy is manipulated. +func (l *LeafData) Copy() *LeafData { + newL := LeafData{ + BlockHash: l.BlockHash, + OutPoint: l.OutPoint, + Height: l.Height, + IsCoinBase: l.IsCoinBase, + Amount: l.Amount, + ReconstructablePkType: l.ReconstructablePkType, + PkScript: make([]byte, len(l.PkScript)), + } + + copy(newL.PkScript, l.PkScript) + return &newL +} + func (l LeafData) MarshalJSON() ([]byte, error) { s := struct { BlockHash string `json:"blockhash"` diff --git a/wire/leaf_test.go b/wire/leaf_test.go index 2c938798..5c6e6233 100644 --- a/wire/leaf_test.go +++ b/wire/leaf_test.go @@ -633,3 +633,42 @@ func TestLeafHash(t *testing.T) { } } } + +// TestLeafDataCopy tests that modifying the leafdata copy does not modify the original. +func TestLeafDataCopy(t *testing.T) { + ld := LeafData{ + BlockHash: *newHashFromStr("00000000000172ff8a4e14441512072bacaf8d38b995a3fcd2f8435efc61717d"), + OutPoint: OutPoint{ + Hash: *newHashFromStr("061bb0bf3a1b9df13773da06bf92920394887a9c2b8b8772ac06be4e077df5eb"), + Index: 10, + }, + Amount: 200000, + PkScript: hexToBytes("a914e8d74935cfa223f9750a32b18d609cba17a5c3fe87"), + Height: 1599255, + IsCoinBase: false, + } + + ldOrig := LeafData{ + BlockHash: *newHashFromStr("00000000000172ff8a4e14441512072bacaf8d38b995a3fcd2f8435efc61717d"), + OutPoint: OutPoint{ + Hash: *newHashFromStr("061bb0bf3a1b9df13773da06bf92920394887a9c2b8b8772ac06be4e077df5eb"), + Index: 10, + }, + Amount: 200000, + PkScript: hexToBytes("a914e8d74935cfa223f9750a32b18d609cba17a5c3fe87"), + Height: 1599255, + IsCoinBase: false, + } + + ldCopy := ld.Copy() + ldCopy.OutPoint.Index = 7777 + ldCopy.OutPoint.Hash[31] = 0x17 + ldCopy.PkScript[0] = 0x77 + if reflect.DeepEqual(ldCopy, ld) { + t.Fatalf("ldCopy and ld are same") + } + + if !reflect.DeepEqual(ld, ldOrig) { + t.Fatalf("ld and ldOrig are different") + } +} From 85f3157436e059f0d6608ccbac97177b2f5c96d3 Mon Sep 17 00:00:00 2001 From: Calvin Kim Date: Wed, 23 Oct 2024 16:39:46 +0900 Subject: [PATCH 2/3] wire: add Copy() to udata --- wire/udata.go | 22 ++++++++++++++++++++++ wire/udata_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/wire/udata.go b/wire/udata.go index d5a59c9a..bb0801b6 100644 --- a/wire/udata.go +++ b/wire/udata.go @@ -23,6 +23,28 @@ type UData struct { LeafDatas []LeafData } +// Copy creates a deep copy of the utreexo data so the original does not get modified +// when the copy is manipulated. +func (ud *UData) Copy() *UData { + proofCopy := utreexo.Proof{ + Targets: make([]uint64, len(ud.AccProof.Targets)), + Proof: make([]utreexo.Hash, len(ud.AccProof.Proof)), + } + copy(proofCopy.Targets, ud.AccProof.Targets) + copy(proofCopy.Proof, ud.AccProof.Proof) + + newUD := UData{ + AccProof: proofCopy, + LeafDatas: make([]LeafData, len(ud.LeafDatas)), + } + + for i := range newUD.LeafDatas { + newUD.LeafDatas[i] = *ud.LeafDatas[i].Copy() + } + + return &newUD +} + // StxosHashes returns the hash of all stxos in this UData. The hashes returned // here represent the hash commitments of the stxos. func (ud *UData) StxoHashes() []utreexo.Hash { diff --git a/wire/udata_test.go b/wire/udata_test.go index 3fa4821d..7fed4cc3 100644 --- a/wire/udata_test.go +++ b/wire/udata_test.go @@ -631,3 +631,47 @@ func TestGenerateUData(t *testing.T) { t.Fatal(err) } } + +// TestUDataCopy tests that modifying the leafdata copy does not modify the original. +func TestUDataCopy(t *testing.T) { + // New forest object. + p := utreexo.NewAccumulator() + + // Create hashes to add from the stxo data. + testDatas := getTestDatas() + addHashes := make([]utreexo.Leaf, 0, len(testDatas[0].leavesPerBlock)) + for i, ld := range testDatas[0].leavesPerBlock { + addHashes = append(addHashes, utreexo.Leaf{ + Hash: ld.LeafHash(), + // Just half and half. + Remember: i%2 == 0, + }) + } + // Add to the accumulator. + err := p.Modify(addHashes, nil, utreexo.Proof{}) + if err != nil { + t.Fatal(err) + } + + // Generate Proof. + ud, err := GenerateUData(testDatas[0].leavesPerBlock, &p) + if err != nil { + t.Fatal(err) + } + udOrig, err := GenerateUData(testDatas[0].leavesPerBlock, &p) + if err != nil { + t.Fatal(err) + } + + udCopy := ud.Copy() + udCopy.AccProof.Targets[0] = 1 << 17 + udCopy.LeafDatas[0].Amount = 55 + + if reflect.DeepEqual(udCopy, ud) { + t.Fatalf("udCopy and ud are same") + } + + if !reflect.DeepEqual(ud, udOrig) { + t.Fatalf("ud and udOrig are different") + } +} From a76f89eecf787c88f9e6426398a079f1b2e1d220 Mon Sep 17 00:00:00 2001 From: Calvin Kim Date: Wed, 23 Oct 2024 16:28:05 +0900 Subject: [PATCH 3/3] wire: add msgutreexotx Utreexo tx is a transaction message with utreexo proof attached. Favoring this over the current method which is to have a separate utreexo encoding for the transaction message. --- wire/message.go | 4 ++ wire/message_test.go | 2 + wire/msgutreexotx.go | 114 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+) create mode 100644 wire/msgutreexotx.go diff --git a/wire/message.go b/wire/message.go index d6447768..5aa4309c 100644 --- a/wire/message.go +++ b/wire/message.go @@ -38,6 +38,7 @@ const ( CmdNotFound = "notfound" CmdBlock = "block" CmdTx = "tx" + CmdUtreexoTx = "utreexotx" CmdGetHeaders = "getheaders" CmdHeaders = "headers" CmdPing = "ping" @@ -131,6 +132,9 @@ func makeEmptyMessage(command string) (Message, error) { case CmdTx: msg = &MsgTx{} + case CmdUtreexoTx: + msg = &MsgUtreexoTx{} + case CmdPing: msg = &MsgPing{} diff --git a/wire/message_test.go b/wire/message_test.go index 7965e561..1f04703a 100644 --- a/wire/message_test.go +++ b/wire/message_test.go @@ -57,6 +57,7 @@ func TestMessage(t *testing.T) { msgGetData := NewMsgGetData() msgNotFound := NewMsgNotFound() msgTx := NewMsgTx(1) + msgUtreexoTx := NewMsgUtreexoTx(1) msgPing := NewMsgPing(123123) msgPong := NewMsgPong(123123) msgGetHeaders := NewMsgGetHeaders() @@ -94,6 +95,7 @@ func TestMessage(t *testing.T) { {msgGetData, msgGetData, pver, MainNet, 25}, {msgNotFound, msgNotFound, pver, MainNet, 25}, {msgTx, msgTx, pver, MainNet, 34}, + {msgUtreexoTx, msgUtreexoTx, pver, MainNet, 37}, {msgPing, msgPing, pver, MainNet, 32}, {msgPong, msgPong, pver, MainNet, 32}, {msgGetHeaders, msgGetHeaders, pver, MainNet, 61}, diff --git a/wire/msgutreexotx.go b/wire/msgutreexotx.go new file mode 100644 index 00000000..7852ba5b --- /dev/null +++ b/wire/msgutreexotx.go @@ -0,0 +1,114 @@ +// Copyright (c) 2024 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wire + +import ( + "io" +) + +// MsgUtreexoTx implements the Message interface and represents a bitcoin utreexo +// tx message. It is used to deliver transaction information in response to a getdata +// message (MsgGetData) for a given transaction with the utreexo proof to verify the +// transaction. +// +// Use the AddTxIn and AddTxOut functions to build up the list of transaction +// inputs and outputs. +type MsgUtreexoTx struct { + // MsgTx is the underlying Bitcoin transaction message. + MsgTx + + // UData is the underlying utreexo data. + UData +} + +// Copy creates a deep copy of a transaction so that the original does not get +// modified when the copy is manipulated. +func (msg *MsgUtreexoTx) Copy() *MsgUtreexoTx { + msgTx := msg.MsgTx.Copy() + newTx := MsgUtreexoTx{ + MsgTx: *msgTx, + UData: *msg.UData.Copy(), + } + + return &newTx +} + +// BtcDecode decodes r using the bitcoin protocol encoding into the receiver. +// This is part of the Message interface implementation. +// See Deserialize for decoding transactions stored to disk, such as in a +// database, as opposed to decoding transactions from the wire. +func (msg *MsgUtreexoTx) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) error { + // Decode the MsgTx. + var msgTx MsgTx + err := msgTx.BtcDecode(r, pver, enc) + if err != nil { + return err + } + msg.MsgTx = msgTx + + // Decode the utreexo data. + ud := new(UData) + ud.LeafDatas = nil + err = ud.Deserialize(r) + if err != nil { + return err + } + msg.UData = *ud + + return nil +} + +// Deserialize decodes a transaction from r into the receiver using a format +// that is suitable for long-term storage such as a database while respecting +// the Version field in the transaction. This function differs from BtcDecode +// in that BtcDecode decodes from the bitcoin wire protocol as it was sent +// across the network. The wire encoding can technically differ depending on +// the protocol version and doesn't even really need to match the format of a +// stored transaction at all. As of the time this comment was written, the +// encoded transaction is the same in both instances, but there is a distinct +// difference and separating the two allows the API to be flexible enough to +// deal with changes. +func (msg *MsgUtreexoTx) Deserialize(r io.Reader) error { + // At the current time, there is no difference between the wire encoding + // at protocol version 0 and the stable long-term storage format. As + // a result, make use of BtcDecode. + return msg.BtcDecode(r, 0, WitnessEncoding) +} + +// BtcEncode encodes the receiver to w using the bitcoin protocol encoding. +// This is part of the Message interface implementation. +// See Serialize for encoding transactions to be stored to disk, such as in a +// database, as opposed to encoding transactions for the wire. +func (msg *MsgUtreexoTx) BtcEncode(w io.Writer, pver uint32, enc MessageEncoding) error { + // Encode the msgTx. + err := msg.MsgTx.BtcEncode(w, pver, enc) + if err != nil { + return err + } + + // Encode the utreexo data. + return msg.UData.Serialize(w) +} + +// Command returns the protocol command string for the message. This is part +// of the Message interface implementation. +func (msg *MsgUtreexoTx) Command() string { + return CmdUtreexoTx +} + +// MaxPayloadLength returns the maximum length the payload can be for the +// receiver. This is part of the Message interface implementation. +func (msg *MsgUtreexoTx) MaxPayloadLength(pver uint32) uint32 { + return MaxBlockPayload +} + +// NewMsgUtreexoTx returns a new bitcoin utreexotx message that conforms to the +// Message interface. The return instance has a default tx message and the udata +// is initialized to the default values. +func NewMsgUtreexoTx(version int32) *MsgUtreexoTx { + return &MsgUtreexoTx{ + MsgTx: *NewMsgTx(1), + } +}