From 0cd200777d71fecb0d6b8233af2c9d3117028e7c Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Fri, 3 Feb 2023 16:38:05 +0100 Subject: [PATCH 1/6] update (Des|S)erializeProof to the electra proof format --- proof_ipa.go | 204 +++++++++++++++++++++++++--------------------- proof_test.go | 72 +++------------- stateless_test.go | 32 ++------ tree_test.go | 10 +-- 4 files changed, 136 insertions(+), 182 deletions(-) diff --git a/proof_ipa.go b/proof_ipa.go index 53680854..a5604e20 100644 --- a/proof_ipa.go +++ b/proof_ipa.go @@ -27,15 +27,30 @@ package verkle import ( "bytes" - "encoding/binary" "errors" "sort" + "unsafe" ipa "github.com/crate-crypto/go-ipa" - "github.com/crate-crypto/go-ipa/bandersnatch/fp" "github.com/crate-crypto/go-ipa/common" ) +const IPA_PROOF_DEPTH = 8 + +type IPAProof struct { + CL [IPA_PROOF_DEPTH][32]byte `json:"cl"` + CR [IPA_PROOF_DEPTH][32]byte `json:"cr"` + FinalEvaluation [32]byte `json:"finalEvaluation"` +} + +type VerkleProof struct { + OtherStems [][31]byte `json:"otherStems"` + DepthExtensionPresent []byte `json:"depthExtensionPresent"` + CommitmentsByPath [][32]byte `json:"commitmentsByPath"` + D [32]byte `json:"d"` + IPAProof *IPAProof `json:"ipa_proof"` +} + type Proof struct { Multipoint *ipa.MultiProof // multipoint argument ExtStatus []byte // the extension status of each stem @@ -45,6 +60,20 @@ type Proof struct { Values [][]byte } +type SuffixStateDiff struct { + Suffix byte `json:"suffix"` + CurrentValue *[32]byte `json:"currentValue"` +} + +type SuffixStateDiffs []SuffixStateDiff + +type StemStateDiff struct { + Stem [31]byte `json:"stem"` + SuffixDiffs SuffixStateDiffs `json:"suffixDiffs"` +} + +type StateDiff []StemStateDiff + func GetCommitmentsForMultiproof(root VerkleNode, keys [][]byte) (*ProofElements, []byte, [][]byte) { sort.Sort(keylist(keys)) return root.GetProofItems(keylist(keys)) @@ -105,130 +134,124 @@ func VerifyVerkleProof(proof *Proof, Cs []*Point, indices []uint8, ys []*Fr, tc return ipa.CheckMultiProof(tr, tc.conf, proof.Multipoint, Cs, ys, indices) } -// A structure representing a tuple -type KeyValuePair struct { - Key []byte `json:"key"` - Value []byte `json:"value"` -} - // SerializeProof serializes the proof in the rust-verkle format: // * len(Proof of absence stem) || Proof of absence stems // * len(depths) || serialize(depth || ext statusi) // * len(commitments) || serialize(commitment) // * Multipoint proof // it also returns the serialized keys and values -func SerializeProof(proof *Proof) ([]byte, []KeyValuePair, error) { - var bufProof bytes.Buffer - - binary.Write(&bufProof, binary.LittleEndian, uint32(len(proof.PoaStems))) - for _, stem := range proof.PoaStems { - _, err := bufProof.Write(stem) - if err != nil { - return nil, nil, err - } +func SerializeProof(proof *Proof) (*VerkleProof, StateDiff, error) { + otherstems := make([][31]byte, len(proof.PoaStems)) + for i, stem := range proof.PoaStems { + copy(otherstems[i][:], stem[:]) } - binary.Write(&bufProof, binary.LittleEndian, uint32(len(proof.ExtStatus))) - for _, daes := range proof.ExtStatus { - err := bufProof.WriteByte(daes) - if err != nil { - return nil, nil, err - } - } - - binary.Write(&bufProof, binary.LittleEndian, uint32(len(proof.Cs))) - for _, C := range proof.Cs { + cbp := make([][32]byte, len(proof.Cs)) + for i, C := range proof.Cs { serialized := C.Bytes() - _, err := bufProof.Write(serialized[:]) - if err != nil { - return nil, nil, err - } + copy(cbp[i][:], serialized[:]) } - proof.Multipoint.Write(&bufProof) + var cls, crs [IPA_PROOF_DEPTH][32]byte + for i := 0; i < IPA_PROOF_DEPTH; i++ { - keyvals := make([]KeyValuePair, 0, len(proof.Keys)) + l := proof.Multipoint.IPA.L[i].Bytes() + copy(cls[i][:], l[:]) + r := proof.Multipoint.IPA.R[i].Bytes() + copy(crs[i][:], r[:]) + } + + var stemdiff *StemStateDiff + var statediff StateDiff for i, key := range proof.Keys { - var ( - valueLen = len(proof.Values[i]) - aligned []byte - ) + if stemdiff == nil || !bytes.Equal(stemdiff.Stem[:], key[:31]) { + statediff = append(statediff, StemStateDiff{}) + stemdiff = &statediff[len(statediff)-1] + copy(stemdiff.Stem[:], key[:31]) + } + var valueLen = len(proof.Values[i]) switch valueLen { - case 0, 32: - aligned = proof.Values[i] + case 0: + stemdiff.SuffixDiffs = append(stemdiff.SuffixDiffs, SuffixStateDiff{ + Suffix: key[31], + }) + case 32: + stemdiff.SuffixDiffs = append(stemdiff.SuffixDiffs, SuffixStateDiff{ + Suffix: key[31], + CurrentValue: (*[32]byte)(proof.Values[i]), + }) default: - aligned = make([]byte, 32) + var aligned [32]byte copy(aligned[:valueLen], proof.Values[i]) + stemdiff.SuffixDiffs = append(stemdiff.SuffixDiffs, SuffixStateDiff{ + Suffix: key[31], + CurrentValue: (*[32]byte)(unsafe.Pointer(&aligned[0])), + }) } - keyvals = append(keyvals, KeyValuePair{key, aligned}) } - - return bufProof.Bytes(), keyvals, nil + return &VerkleProof{ + OtherStems: otherstems, + DepthExtensionPresent: proof.ExtStatus, + CommitmentsByPath: cbp, + D: proof.Multipoint.D.Bytes(), + IPAProof: &IPAProof{ + CL: cls, + CR: crs, + FinalEvaluation: proof.Multipoint.IPA.A_scalar.Bytes(), + }, + }, statediff, nil } // DeserializeProof deserializes the proof found in blocks, into a format that // can be used to rebuild a stateless version of the tree. -func DeserializeProof(proofSerialized []byte, keyvals []KeyValuePair) (*Proof, error) { +func DeserializeProof(vp *VerkleProof, statediff StateDiff) (*Proof, error) { var ( - numPoaStems, numExtStatus uint32 - numCommitments uint32 - poaStems, keys, values [][]byte - extStatus []byte - commitments []*Point - multipoint ipa.MultiProof + poaStems, keys, values [][]byte + extStatus []byte + commitments []*Point + multipoint ipa.MultiProof ) - reader := bytes.NewReader(proofSerialized) - - if err := binary.Read(reader, binary.LittleEndian, &numPoaStems); err != nil { - return nil, err - } - poaStems = make([][]byte, numPoaStems) - for i := 0; i < int(numPoaStems); i++ { - var poaStem [31]byte - if err := binary.Read(reader, binary.LittleEndian, &poaStem); err != nil { - return nil, err - } + poaStems = make([][]byte, len(vp.OtherStems)) + for i, poaStem := range vp.OtherStems { poaStems[i] = poaStem[:] } - if err := binary.Read(reader, binary.LittleEndian, &numExtStatus); err != nil { - return nil, err - } - extStatus = make([]byte, numExtStatus) - for i := 0; i < int(numExtStatus); i++ { - var e byte - if err := binary.Read(reader, binary.LittleEndian, &e); err != nil { - return nil, err - } - extStatus[i] = e - } + extStatus = vp.DepthExtensionPresent - if err := binary.Read(reader, binary.LittleEndian, &numCommitments); err != nil { - return nil, err - } - commitments = make([]*Point, numCommitments) - commitmentBytes := make([]byte, fp.Bytes) - for i := 0; i < int(numCommitments); i++ { + commitments = make([]*Point, len(vp.CommitmentsByPath)) + for i, commitmentBytes := range vp.CommitmentsByPath { var commitment Point - if err := binary.Read(reader, binary.LittleEndian, commitmentBytes); err != nil { - return nil, err - } - - if err := commitment.SetBytes(commitmentBytes); err != nil { + if err := commitment.SetBytesTrusted(commitmentBytes[:]); err != nil { return nil, err } - commitments[i] = &commitment } - // TODO submit PR to go-ipa to make this return an error if it fails to Read - multipoint.Read(reader) + multipoint.D.SetBytes(vp.D[:]) + multipoint.IPA.A_scalar.SetBytes(vp.IPAProof.FinalEvaluation[:]) + multipoint.IPA.L = make([]Point, IPA_PROOF_DEPTH) + for i, b := range vp.IPAProof.CL { + multipoint.IPA.L[i].SetBytes(b[:]) + } + multipoint.IPA.R = make([]Point, IPA_PROOF_DEPTH) + for i, b := range vp.IPAProof.CR { + multipoint.IPA.R[i].SetBytes(b[:]) + } - // Turn keyvals into keys and values - for _, kv := range keyvals { - keys = append(keys, kv.Key) - values = append(values, kv.Value) + // turn statediff into keys and values + for _, stemdiff := range statediff { + for _, suffixdiff := range stemdiff.SuffixDiffs { + var k [32]byte + copy(k[:31], stemdiff.Stem[:]) + k[31] = suffixdiff.Suffix + keys = append(keys, k[:]) + if suffixdiff.CurrentValue != nil { + values = append(values, suffixdiff.CurrentValue[:]) + } else { + values = append(values, nil) + } + } } proof := Proof{ @@ -239,7 +262,6 @@ func DeserializeProof(proofSerialized []byte, keyvals []KeyValuePair) (*Proof, e keys, values, } - return &proof, nil } diff --git a/proof_test.go b/proof_test.go index 60c9dd88..8e96d370 100644 --- a/proof_test.go +++ b/proof_test.go @@ -28,7 +28,6 @@ package verkle import ( "bytes" "crypto/rand" - "encoding/binary" "encoding/hex" "testing" ) @@ -265,26 +264,21 @@ func TestProofSerializationNoAbsentStem(t *testing.T) { proof, _, _, _, _ := MakeVerkleMultiProof(root, [][]byte{keys[0]}, map[string][]byte{}) - serialized, _, err := SerializeProof(proof) + vp, statediff, err := SerializeProof(proof) if err != nil { t.Fatal(err) } - if len(serialized) == 0 { - t.Fatal("zero-length serialized proof payload") + if len(vp.OtherStems) > 0 { + t.Fatalf("first byte indicates that there are %d stems that should not be here", len(vp.OtherStems)) } - stemsize := binary.LittleEndian.Uint32(serialized[:4]) - if stemsize != 0 { - t.Fatalf("first byte indicates that there are %d stems that should not be here", stemsize) - } - extsize := binary.LittleEndian.Uint32(serialized[4:8]) + extsize := len(statediff) if extsize != 1 { t.Fatalf("second byte indicates that there are %d extension statuses, should be 1", extsize) } - // TODO keep checking the serialized values here } func TestProofSerializationWithAbsentStem(t *testing.T) { - const leafCount = 1000 + const leafCount = 256 keys := make([][]byte, leafCount) root := New() @@ -304,18 +298,15 @@ func TestProofSerializationWithAbsentStem(t *testing.T) { proof, _, _, _, _ := MakeVerkleMultiProof(root, [][]byte{absentkey[:]}, map[string][]byte{}) - serialized, _, err := SerializeProof(proof) + vp, statediff, err := SerializeProof(proof) if err != nil { t.Fatal(err) } - if len(serialized) == 0 { - t.Fatal("zero-length serialized proof payload") - } - stemsize := binary.LittleEndian.Uint32(serialized[:4]) + stemsize := len(vp.OtherStems) if stemsize != 1 { t.Fatalf("first byte indicates that there are %d stems that should not be here", stemsize) } - extsize := binary.LittleEndian.Uint32(serialized[4+stemsize*31 : 4+stemsize*31+4]) + extsize := len(statediff) if extsize != 1 { t.Fatalf("second byte indicates that there are %d extension statuses, should be 1", extsize) } @@ -323,20 +314,15 @@ func TestProofSerializationWithAbsentStem(t *testing.T) { } func TestProofDeserialize(t *testing.T) { - const leafCount = 1000 + const leafCount = 256 keys := make([][]byte, leafCount) root := New() - var keyvals []KeyValuePair for i := 0; i < leafCount; i++ { key := make([]byte, 32) key[2] = byte(i) keys[i] = key root.Insert(key, fourtyKeyTest, nil) - keyvals = append(keyvals, KeyValuePair{ - Key: key, - Value: fourtyKeyTest, - }) } // Create stem 0x0000020100000.... that is not present in the tree, @@ -348,15 +334,12 @@ func TestProofDeserialize(t *testing.T) { proof, _, _, _, _ := MakeVerkleMultiProof(root, [][]byte{absentkey[:]}, map[string][]byte{}) - serialized, _, err := SerializeProof(proof) + vp, statediff, err := SerializeProof(proof) if err != nil { t.Fatal(err) } - if len(serialized) == 0 { - t.Fatal("zero-length serialized proof payload") - } - deserialized, err := DeserializeProof(serialized, keyvals) + deserialized, err := DeserializeProof(vp, statediff) if err != nil { t.Fatalf("could not deserialize verkle proof: %v", err) } @@ -370,38 +353,7 @@ func TestProofDeserialize(t *testing.T) { } func TestProofDeserializeErrors(t *testing.T) { - - deserialized, err := DeserializeProof([]byte{0}, nil) - if err == nil { - t.Fatal("deserializing invalid proof didn't cause an error") - } - if deserialized != nil { - t.Fatalf("non-nil deserialized data returned %v", deserialized) - } - - deserialized, err = DeserializeProof([]byte{1, 0, 0, 0}, nil) - if err == nil { - t.Fatal("deserializing invalid proof didn't cause an error") - } - if deserialized != nil { - t.Fatalf("non-nil deserialized data returned %v", deserialized) - } - - deserialized, err = DeserializeProof([]byte{0, 0, 0, 0, 0}, nil) - if err == nil { - t.Fatal("deserializing invalid proof didn't cause an error") - } - if deserialized != nil { - t.Fatalf("non-nil deserialized data returned %v", deserialized) - } - - deserialized, err = DeserializeProof([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0}, nil) - if err == nil { - t.Fatal("deserializing invalid proof didn't cause an error") - } - if deserialized != nil { - t.Fatalf("non-nil deserialized data returned %v", deserialized) - } + // TODO } func TestProofOfAbsenceEdgeCase(t *testing.T) { diff --git a/stateless_test.go b/stateless_test.go index 5c4b6b2b..35bce51e 100644 --- a/stateless_test.go +++ b/stateless_test.go @@ -263,19 +263,15 @@ func TestStatelessDeserialize(t *testing.T) { for _, k := range [][]byte{zeroKeyTest, oneKeyTest, fourtyKeyTest, ffx32KeyTest} { root.Insert(k, fourtyKeyTest, nil) } - keyvals := []KeyValuePair{ - {zeroKeyTest, fourtyKeyTest}, - {fourtyKeyTest, fourtyKeyTest}, - } proof, _, _, _, _ := MakeVerkleMultiProof(root, keylist{zeroKeyTest, fourtyKeyTest}, map[string][]byte{string(zeroKeyTest): fourtyKeyTest, string(fourtyKeyTest): fourtyKeyTest}) - serialized, _, err := SerializeProof(proof) + serialized, statediff, err := SerializeProof(proof) if err != nil { t.Fatalf("could not serialize proof: %v", err) } - dproof, err := DeserializeProof(serialized, keyvals) + dproof, err := DeserializeProof(serialized, statediff) if err != nil { t.Fatalf("error deserializing proof: %v", err) } @@ -304,19 +300,15 @@ func TestStatelessDeserializeMissginChildNode(t *testing.T) { for _, k := range [][]byte{zeroKeyTest, oneKeyTest, ffx32KeyTest} { root.Insert(k, fourtyKeyTest, nil) } - keyvals := []KeyValuePair{ - {zeroKeyTest, fourtyKeyTest}, - {fourtyKeyTest, nil}, - } proof, _, _, _, _ := MakeVerkleMultiProof(root, keylist{zeroKeyTest, fourtyKeyTest}, map[string][]byte{string(zeroKeyTest): fourtyKeyTest, string(fourtyKeyTest): nil}) - serialized, _, err := SerializeProof(proof) + serialized, statediff, err := SerializeProof(proof) if err != nil { t.Fatalf("could not serialize proof: %v", err) } - dproof, err := DeserializeProof(serialized, keyvals) + dproof, err := DeserializeProof(serialized, statediff) if err != nil { t.Fatalf("error deserializing proof: %v", err) } @@ -345,19 +337,15 @@ func TestStatelessDeserializeDepth2(t *testing.T) { for _, k := range [][]byte{zeroKeyTest, key1} { root.Insert(k, fourtyKeyTest, nil) } - keyvals := []KeyValuePair{ - {zeroKeyTest, fourtyKeyTest}, - {key1, nil}, - } proof, _, _, _, _ := MakeVerkleMultiProof(root, keylist{zeroKeyTest, key1}, map[string][]byte{string(zeroKeyTest): fourtyKeyTest, string(key1): nil}) - serialized, _, err := SerializeProof(proof) + serialized, statediff, err := SerializeProof(proof) if err != nil { t.Fatalf("could not serialize proof: %v", err) } - dproof, err := DeserializeProof(serialized, keyvals) + dproof, err := DeserializeProof(serialized, statediff) if err != nil { t.Fatalf("error deserializing proof: %v", err) } @@ -384,19 +372,15 @@ func TestStatelessGetProofItems(t *testing.T) { for _, k := range insertedKeys { root.Insert(k, fourtyKeyTest, nil) } - keyvals := []KeyValuePair{ - {zeroKeyTest, fourtyKeyTest}, - {fourtyKeyTest, nil}, - } proof, _, _, _, _ := MakeVerkleMultiProof(root, keylist(provenKeys), map[string][]byte{string(zeroKeyTest): fourtyKeyTest, string(fourtyKeyTest): nil}) - serialized, _, err := SerializeProof(proof) + serialized, statediff, err := SerializeProof(proof) if err != nil { t.Fatalf("could not serialize proof: %v", err) } - dproof, err := DeserializeProof(serialized, keyvals) + dproof, err := DeserializeProof(serialized, statediff) if err != nil { t.Fatalf("error deserializing proof: %v", err) } diff --git a/tree_test.go b/tree_test.go index 2d080502..fed03931 100644 --- a/tree_test.go +++ b/tree_test.go @@ -1147,22 +1147,18 @@ func TestRustBanderwagonBlock48(t *testing.T) { r := tree.Commit() proof, cis, zis, yis, _ := MakeVerkleMultiProof(tree, keys, initialVals) - serialized, _, err := SerializeProof(proof) + vp, statediff, err := SerializeProof(proof) if err != nil { t.Fatal(err) } - t.Logf("serialized proof=%x", serialized) + t.Logf("serialized proof=%v", vp) cfg := GetConfig() if !VerifyVerkleProof(proof, cis, zis, yis, cfg) { t.Fatal("proof didn't verify") } - var kvp []KeyValuePair - for i := range keys { - kvp = append(kvp, KeyValuePair{Key: keys[i], Value: vals[i]}) - } - dproof, err := DeserializeProof(serialized, kvp) + dproof, err := DeserializeProof(vp, statediff) if err != nil { t.Fatal(err) } From e88dff583c8babfb62e93eaa42cae63fb13aa65e Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Fri, 24 Feb 2023 16:26:27 +0100 Subject: [PATCH 2/6] Fix/electra proof format json marshalling (#330) * fix: add custom JSON serializers * move marshallers to their own file --- proof_json.go | 210 ++++++++++++++++++++++++++++++++++++++++++++++++++ proof_test.go | 84 ++++++++++++++++++++ 2 files changed, 294 insertions(+) create mode 100644 proof_json.go diff --git a/proof_json.go b/proof_json.go new file mode 100644 index 00000000..a91909df --- /dev/null +++ b/proof_json.go @@ -0,0 +1,210 @@ +// This is free and unencumbered software released into the public domain. +// +// Anyone is free to copy, modify, publish, use, compile, sell, or +// distribute this software, either in source code form or as a compiled +// binary, for any purpose, commercial or non-commercial, and by any +// means. +// +// In jurisdictions that recognize copyright laws, the author or authors +// of this software dedicate any and all copyright interest in the +// software to the public domain. We make this dedication for the benefit +// of the public at large and to the detriment of our heirs and +// successors. We intend this dedication to be an overt act of +// relinquishment in perpetuity of all present and future rights to this +// software under copyright law. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// For more information, please refer to + +package verkle + +import ( + "encoding/hex" + "encoding/json" + "fmt" +) + +type ipaproofMarshaller struct { + CL [IPA_PROOF_DEPTH]string `json:"cl"` + CR [IPA_PROOF_DEPTH]string `json:"cr"` + FinalEvaluation string `json:"finalEvaluation"` +} + +func (ipp *IPAProof) MarshalJSON() ([]byte, error) { + return json.Marshal(&ipaproofMarshaller{ + CL: [IPA_PROOF_DEPTH]string{ + hex.EncodeToString(ipp.CL[0][:]), + hex.EncodeToString(ipp.CL[1][:]), + hex.EncodeToString(ipp.CL[2][:]), + hex.EncodeToString(ipp.CL[3][:]), + hex.EncodeToString(ipp.CL[4][:]), + hex.EncodeToString(ipp.CL[5][:]), + hex.EncodeToString(ipp.CL[6][:]), + hex.EncodeToString(ipp.CL[7][:]), + }, + CR: [IPA_PROOF_DEPTH]string{ + hex.EncodeToString(ipp.CR[0][:]), + hex.EncodeToString(ipp.CR[1][:]), + hex.EncodeToString(ipp.CR[2][:]), + hex.EncodeToString(ipp.CR[3][:]), + hex.EncodeToString(ipp.CR[4][:]), + hex.EncodeToString(ipp.CR[5][:]), + hex.EncodeToString(ipp.CR[6][:]), + hex.EncodeToString(ipp.CR[7][:]), + }, + FinalEvaluation: hex.EncodeToString(ipp.FinalEvaluation[:]), + }) +} + +func (ipp *IPAProof) UnmarshalJSON(data []byte) error { + aux := &ipaproofMarshaller{} + + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + if len(aux.FinalEvaluation) != 64 { + return fmt.Errorf("invalid hex string for final evaluation: %s", aux.FinalEvaluation) + } + + currentValueBytes, err := hex.DecodeString(aux.FinalEvaluation) + if err != nil { + return fmt.Errorf("error decoding hex string for current value: %v", err) + } + copy(ipp.FinalEvaluation[:], currentValueBytes) + + for i := range ipp.CL { + if len(aux.CL[i]) != 64 { + return fmt.Errorf("invalid hex string for CL[%d]: %s", i, aux.CL[i]) + } + val, err := hex.DecodeString(aux.CL[i]) + if err != nil { + return fmt.Errorf("error decoding hex string for CL[%d]: %s", i, aux.CL[i]) + } + copy(ipp.CL[i][:], val[:]) + if len(aux.CR[i]) != 64 { + return fmt.Errorf("invalid hex string for CR[%d]: %s", i, aux.CR[i]) + } + val, err = hex.DecodeString(aux.CR[i]) + if err != nil { + return fmt.Errorf("error decoding hex string for CR[%d]: %s", i, aux.CR[i]) + } + copy(ipp.CR[i][:], val[:]) + } + copy(ipp.FinalEvaluation[:], currentValueBytes) + + return nil +} + +type verkleProofMarshaller struct { + OtherStems []string `json:"otherStems"` + DepthExtensionPresent string `json:"depthExtensionPresent"` + CommitmentsByPath []string `json:"commitmentsByPath"` + D string `json:"d"` + IPAProof *IPAProof `json:"ipa_proof"` +} + +func (vp *VerkleProof) MarshalJSON() ([]byte, error) { + aux := &verkleProofMarshaller{ + OtherStems: make([]string, len(vp.OtherStems)), + DepthExtensionPresent: hex.EncodeToString(vp.DepthExtensionPresent[:]), + CommitmentsByPath: make([]string, len(vp.CommitmentsByPath)), + D: hex.EncodeToString(vp.D[:]), + IPAProof: vp.IPAProof, + } + + for i, s := range vp.OtherStems { + aux.OtherStems[i] = hex.EncodeToString(s[:]) + } + for i, c := range vp.CommitmentsByPath { + aux.CommitmentsByPath[i] = hex.EncodeToString(c[:]) + } + return json.Marshal(aux) +} + +func (vp *VerkleProof) UnmarshalJSON(data []byte) error { + var aux verkleProofMarshaller + err := json.Unmarshal(data, &aux) + if err != nil { + return err + } + + vp.DepthExtensionPresent, err = hex.DecodeString(aux.DepthExtensionPresent) + if err != nil { + return fmt.Errorf("error decoding hex string for depth and extention present: %v", err) + } + + vp.CommitmentsByPath = make([][32]byte, len(aux.CommitmentsByPath)) + for i, c := range aux.CommitmentsByPath { + val, err := hex.DecodeString(c) + if err != nil { + return fmt.Errorf("error decoding hex string for commitment #%d: %w", i, err) + } + copy(vp.CommitmentsByPath[i][:], val) + } + + currentValueBytes, err := hex.DecodeString(aux.D) + if err != nil { + return fmt.Errorf("error decoding hex string for D: %w", err) + } + copy(vp.D[:], currentValueBytes) + + vp.OtherStems = make([][31]byte, len(aux.OtherStems)) + for i, c := range aux.OtherStems { + val, err := hex.DecodeString(c) + if err != nil { + return fmt.Errorf("error decoding hex string for other stem #%d: %w", i, err) + } + copy(vp.OtherStems[i][:], val) + } + + vp.IPAProof = aux.IPAProof + return nil +} + +type suffixStateDiffMarshaller struct { + Suffix byte `json:"suffix"` + CurrentValue string `json:"currentValue"` +} + +func (ssd SuffixStateDiff) MarshalJSON() ([]byte, error) { + return json.Marshal(&suffixStateDiffMarshaller{ + Suffix: ssd.Suffix, + CurrentValue: hex.EncodeToString(ssd.CurrentValue[:]), + }) +} + +func (ssd *SuffixStateDiff) UnmarshalJSON(data []byte) error { + aux := &suffixStateDiffMarshaller{ + CurrentValue: "", + } + + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + if len(aux.CurrentValue) != 64 { + return fmt.Errorf("invalid hex string for current value: %s", aux.CurrentValue) + } + + currentValueBytes, err := hex.DecodeString(aux.CurrentValue) + if err != nil { + return fmt.Errorf("error decoding hex string for current value: %v", err) + } + + *ssd = SuffixStateDiff{ + Suffix: aux.Suffix, + CurrentValue: &[32]byte{}, + } + + copy(ssd.CurrentValue[:], currentValueBytes) + + return nil +} diff --git a/proof_test.go b/proof_test.go index 8e96d370..1faf584a 100644 --- a/proof_test.go +++ b/proof_test.go @@ -29,6 +29,8 @@ import ( "bytes" "crypto/rand" "encoding/hex" + "encoding/json" + "reflect" "testing" ) @@ -411,3 +413,85 @@ func TestProofOfAbsenceNoneMultipleStems(t *testing.T) { t.Fatalf("invalid number of none extension statuses: %d ≠ 1", len(proof.ExtStatus)) } } + +func TestSuffixStateDiffJSONMarshalUn(t *testing.T) { + ssd := SuffixStateDiff{ + Suffix: 0x41, + CurrentValue: &[32]byte{ + 0x10, 0x20, 0x30, 0x40, + 0x50, 0x60, 0x70, 0x80, + 0x90, 0xA0, 0xB0, 0xC0, + 0xD0, 0xE0, 0xF0, 0x00, + 0x11, 0x22, 0x33, 0x44, + 0x55, 0x66, 0x77, 0x88, + 0x99, 0xAA, 0xBB, 0xCC, + 0xDD, 0xEE, 0xFF, 0x00, + }, + } + + expectedJSON := `{"suffix":65,"currentValue":"102030405060708090a0b0c0d0e0f000112233445566778899aabbccddeeff00"}` + actualJSON, err := json.Marshal(ssd) + if err != nil { + t.Errorf("error marshalling SuffixStateDiff to JSON: %v", err) + } + + if string(actualJSON) != expectedJSON { + t.Errorf("JSON output doesn't match expected value.\nExpected: %s\nActual: %s", expectedJSON, string(actualJSON)) + } + + var actualSSD SuffixStateDiff + err = json.Unmarshal([]byte(actualJSON), &actualSSD) + if err != nil { + t.Errorf("error unmarshalling JSON to SuffixStateDiff: %v", err) + } + + if !reflect.DeepEqual(actualSSD, ssd) { + t.Errorf("SuffixStateDiff doesn't match expected value.\nExpected: %+v\nActual: %+v", ssd, actualSSD) + } +} + +func TestIPAProofMarshalUnmarshalJSON(t *testing.T) { + ip1 := &IPAProof{ + CL: [IPA_PROOF_DEPTH][32]byte{{1}, {2}, {3}}, + CR: [IPA_PROOF_DEPTH][32]byte{{4}, {5}, {6}}, + FinalEvaluation: [32]byte{7}, + } + ipJSON, err := json.Marshal(ip1) + if err != nil { + t.Fatal(err) + } + ip2 := &IPAProof{} + err = json.Unmarshal(ipJSON, ip2) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(ip1, ip2) { + t.Errorf("expected %v, got %v", ip1, ip2) + } +} + +func TestVerkleProofMarshalUnmarshalJSON(t *testing.T) { + vp1 := &VerkleProof{ + OtherStems: [][31]byte{{1}, {2}, {3}}, + DepthExtensionPresent: []byte{4, 5, 6}, + CommitmentsByPath: [][32]byte{{7}, {8}, {9}}, + D: [32]byte{10}, + IPAProof: &IPAProof{ + CL: [IPA_PROOF_DEPTH][32]byte{{11}, {12}, {13}}, + CR: [IPA_PROOF_DEPTH][32]byte{{14}, {15}, {16}}, + FinalEvaluation: [32]byte{17}, + }, + } + vpJSON, err := json.Marshal(vp1) + if err != nil { + t.Fatal(err) + } + vp2 := &VerkleProof{} + err = json.Unmarshal(vpJSON, vp2) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(vp1, vp2) { + t.Errorf("expected %v, got %v", vp1, vp2) + } +} From 179ac58adbe967c5066fc8316bede0d128d2c72e Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Fri, 24 Feb 2023 17:16:45 +0100 Subject: [PATCH 3/6] fix some deepsource comments --- proof_ipa.go | 2 +- proof_json.go | 6 +++--- proof_test.go | 4 ---- tree_test.go | 7 +------ 4 files changed, 5 insertions(+), 14 deletions(-) diff --git a/proof_ipa.go b/proof_ipa.go index a5604e20..8877aa07 100644 --- a/proof_ipa.go +++ b/proof_ipa.go @@ -143,7 +143,7 @@ func VerifyVerkleProof(proof *Proof, Cs []*Point, indices []uint8, ys []*Fr, tc func SerializeProof(proof *Proof) (*VerkleProof, StateDiff, error) { otherstems := make([][31]byte, len(proof.PoaStems)) for i, stem := range proof.PoaStems { - copy(otherstems[i][:], stem[:]) + copy(otherstems[i][:], stem) } cbp := make([][32]byte, len(proof.Cs)) diff --git a/proof_json.go b/proof_json.go index a91909df..cbf51708 100644 --- a/proof_json.go +++ b/proof_json.go @@ -88,7 +88,7 @@ func (ipp *IPAProof) UnmarshalJSON(data []byte) error { if err != nil { return fmt.Errorf("error decoding hex string for CL[%d]: %s", i, aux.CL[i]) } - copy(ipp.CL[i][:], val[:]) + copy(ipp.CL[i][:], val) if len(aux.CR[i]) != 64 { return fmt.Errorf("invalid hex string for CR[%d]: %s", i, aux.CR[i]) } @@ -96,7 +96,7 @@ func (ipp *IPAProof) UnmarshalJSON(data []byte) error { if err != nil { return fmt.Errorf("error decoding hex string for CR[%d]: %s", i, aux.CR[i]) } - copy(ipp.CR[i][:], val[:]) + copy(ipp.CR[i][:], val) } copy(ipp.FinalEvaluation[:], currentValueBytes) @@ -114,7 +114,7 @@ type verkleProofMarshaller struct { func (vp *VerkleProof) MarshalJSON() ([]byte, error) { aux := &verkleProofMarshaller{ OtherStems: make([]string, len(vp.OtherStems)), - DepthExtensionPresent: hex.EncodeToString(vp.DepthExtensionPresent[:]), + DepthExtensionPresent: hex.EncodeToString(vp.DepthExtensionPresent), CommitmentsByPath: make([]string, len(vp.CommitmentsByPath)), D: hex.EncodeToString(vp.D[:]), IPAProof: vp.IPAProof, diff --git a/proof_test.go b/proof_test.go index 1faf584a..c9203eab 100644 --- a/proof_test.go +++ b/proof_test.go @@ -354,10 +354,6 @@ func TestProofDeserialize(t *testing.T) { } } -func TestProofDeserializeErrors(t *testing.T) { - // TODO -} - func TestProofOfAbsenceEdgeCase(t *testing.T) { root := New() root.Commit() diff --git a/tree_test.go b/tree_test.go index fed03931..40e6177a 100644 --- a/tree_test.go +++ b/tree_test.go @@ -1121,19 +1121,14 @@ func TestRustBanderwagonBlock48(t *testing.T) { "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "0000000000000000000000000000000000000000000000000000000000000000", } - var ( - vals [][]byte - initialVals = map[string][]byte{} - ) + var initialVals = map[string][]byte{} for i, s := range valStrings { if s == "" { - vals = append(vals, nil) continue } v, _ := hex.DecodeString(s) - vals = append(vals, v) tree.Insert(keys[i], v, nil) initialVals[string(keys[i])] = v From f69661b7a54e6fd1114e784b9694ff83f214c7c6 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Fri, 24 Feb 2023 17:30:28 +0100 Subject: [PATCH 4/6] fix stacktrace when CurrentValue is nil --- proof_json.go | 26 ++++++++++++++++---------- proof_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/proof_json.go b/proof_json.go index cbf51708..74c7a625 100644 --- a/proof_json.go +++ b/proof_json.go @@ -175,9 +175,13 @@ type suffixStateDiffMarshaller struct { } func (ssd SuffixStateDiff) MarshalJSON() ([]byte, error) { + var cvstr string + if ssd.CurrentValue != nil { + cvstr = hex.EncodeToString(ssd.CurrentValue[:]) + } return json.Marshal(&suffixStateDiffMarshaller{ Suffix: ssd.Suffix, - CurrentValue: hex.EncodeToString(ssd.CurrentValue[:]), + CurrentValue: cvstr, }) } @@ -190,21 +194,23 @@ func (ssd *SuffixStateDiff) UnmarshalJSON(data []byte) error { return err } - if len(aux.CurrentValue) != 64 { + if len(aux.CurrentValue) != 64 && len(aux.CurrentValue) != 0 { return fmt.Errorf("invalid hex string for current value: %s", aux.CurrentValue) } - currentValueBytes, err := hex.DecodeString(aux.CurrentValue) - if err != nil { - return fmt.Errorf("error decoding hex string for current value: %v", err) - } - *ssd = SuffixStateDiff{ - Suffix: aux.Suffix, - CurrentValue: &[32]byte{}, + Suffix: aux.Suffix, } - copy(ssd.CurrentValue[:], currentValueBytes) + if len(aux.CurrentValue) != 0 { + currentValueBytes, err := hex.DecodeString(aux.CurrentValue) + if err != nil { + return fmt.Errorf("error decoding hex string for current value: %v", err) + } + + ssd.CurrentValue = &[32]byte{} + copy(ssd.CurrentValue[:], currentValueBytes) + } return nil } diff --git a/proof_test.go b/proof_test.go index c9203eab..b322f3c1 100644 --- a/proof_test.go +++ b/proof_test.go @@ -446,6 +446,33 @@ func TestSuffixStateDiffJSONMarshalUn(t *testing.T) { } } +func TestSuffixStateDiffJSONMarshalUnCurrentValueNil(t *testing.T) { + ssd := SuffixStateDiff{ + Suffix: 0x41, + CurrentValue: nil, + } + + expectedJSON := `{"suffix":65,"currentValue":""}` + actualJSON, err := json.Marshal(ssd) + if err != nil { + t.Errorf("error marshalling SuffixStateDiff to JSON: %v", err) + } + + if string(actualJSON) != expectedJSON { + t.Errorf("JSON output doesn't match expected value.\nExpected: %s\nActual: %s", expectedJSON, string(actualJSON)) + } + + var actualSSD SuffixStateDiff + err = json.Unmarshal([]byte(actualJSON), &actualSSD) + if err != nil { + t.Errorf("error unmarshalling JSON to SuffixStateDiff: %v", err) + } + + if !reflect.DeepEqual(actualSSD, ssd) { + t.Errorf("SuffixStateDiff doesn't match expected value.\nExpected: %+v\nActual: %+v", ssd, actualSSD) + } +} + func TestIPAProofMarshalUnmarshalJSON(t *testing.T) { ip1 := &IPAProof{ CL: [IPA_PROOF_DEPTH][32]byte{{1}, {2}, {3}}, From 2d38c5d290c177757be4fdbbacb97d07c4033dd5 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Fri, 24 Feb 2023 17:44:02 +0100 Subject: [PATCH 5/6] linter fixes --- proof_json.go | 2 +- proof_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/proof_json.go b/proof_json.go index 74c7a625..99753ac8 100644 --- a/proof_json.go +++ b/proof_json.go @@ -138,7 +138,7 @@ func (vp *VerkleProof) UnmarshalJSON(data []byte) error { vp.DepthExtensionPresent, err = hex.DecodeString(aux.DepthExtensionPresent) if err != nil { - return fmt.Errorf("error decoding hex string for depth and extention present: %v", err) + return fmt.Errorf("error decoding hex string for depth and extension present: %v", err) } vp.CommitmentsByPath = make([][32]byte, len(aux.CommitmentsByPath)) diff --git a/proof_test.go b/proof_test.go index b322f3c1..3262165c 100644 --- a/proof_test.go +++ b/proof_test.go @@ -436,7 +436,7 @@ func TestSuffixStateDiffJSONMarshalUn(t *testing.T) { } var actualSSD SuffixStateDiff - err = json.Unmarshal([]byte(actualJSON), &actualSSD) + err = json.Unmarshal(actualJSON, &actualSSD) if err != nil { t.Errorf("error unmarshalling JSON to SuffixStateDiff: %v", err) } @@ -463,7 +463,7 @@ func TestSuffixStateDiffJSONMarshalUnCurrentValueNil(t *testing.T) { } var actualSSD SuffixStateDiff - err = json.Unmarshal([]byte(actualJSON), &actualSSD) + err = json.Unmarshal(actualJSON, &actualSSD) if err != nil { t.Errorf("error unmarshalling JSON to SuffixStateDiff: %v", err) } From 0b59b1fd4727821136f827a8b4ce8164800b8d85 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Mon, 6 Mar 2023 11:48:38 +0100 Subject: [PATCH 6/6] Fix proof element JSON serialization for compatibility with lighthouse (#331) * fix stem serialization * add a 0x prefix to hex strings * fix: set currentValue to null for a missing key * fix: lowercase for IPAProof in json payload --- proof_json.go | 128 +++++++++++++++++++++++++++++++++----------------- proof_test.go | 43 ++++++++++++++++- 2 files changed, 126 insertions(+), 45 deletions(-) diff --git a/proof_json.go b/proof_json.go index 99753ac8..197d3aa5 100644 --- a/proof_json.go +++ b/proof_json.go @@ -31,6 +31,20 @@ import ( "fmt" ) +// HexToPrefixedString turns a byte slice into its hex representation +// and prefixes it with `0x`. +func HexToPrefixedString(data []byte) string { + return "0x" + hex.EncodeToString(data) +} + +// PrefixedHexStringToBytes does the opposite of HexToPrefixedString. +func PrefixedHexStringToBytes(input string) ([]byte, error) { + if input[0:2] == "0x" { + input = input[2:] + } + return hex.DecodeString(input) +} + type ipaproofMarshaller struct { CL [IPA_PROOF_DEPTH]string `json:"cl"` CR [IPA_PROOF_DEPTH]string `json:"cr"` @@ -40,26 +54,26 @@ type ipaproofMarshaller struct { func (ipp *IPAProof) MarshalJSON() ([]byte, error) { return json.Marshal(&ipaproofMarshaller{ CL: [IPA_PROOF_DEPTH]string{ - hex.EncodeToString(ipp.CL[0][:]), - hex.EncodeToString(ipp.CL[1][:]), - hex.EncodeToString(ipp.CL[2][:]), - hex.EncodeToString(ipp.CL[3][:]), - hex.EncodeToString(ipp.CL[4][:]), - hex.EncodeToString(ipp.CL[5][:]), - hex.EncodeToString(ipp.CL[6][:]), - hex.EncodeToString(ipp.CL[7][:]), + HexToPrefixedString(ipp.CL[0][:]), + HexToPrefixedString(ipp.CL[1][:]), + HexToPrefixedString(ipp.CL[2][:]), + HexToPrefixedString(ipp.CL[3][:]), + HexToPrefixedString(ipp.CL[4][:]), + HexToPrefixedString(ipp.CL[5][:]), + HexToPrefixedString(ipp.CL[6][:]), + HexToPrefixedString(ipp.CL[7][:]), }, CR: [IPA_PROOF_DEPTH]string{ - hex.EncodeToString(ipp.CR[0][:]), - hex.EncodeToString(ipp.CR[1][:]), - hex.EncodeToString(ipp.CR[2][:]), - hex.EncodeToString(ipp.CR[3][:]), - hex.EncodeToString(ipp.CR[4][:]), - hex.EncodeToString(ipp.CR[5][:]), - hex.EncodeToString(ipp.CR[6][:]), - hex.EncodeToString(ipp.CR[7][:]), + HexToPrefixedString(ipp.CR[0][:]), + HexToPrefixedString(ipp.CR[1][:]), + HexToPrefixedString(ipp.CR[2][:]), + HexToPrefixedString(ipp.CR[3][:]), + HexToPrefixedString(ipp.CR[4][:]), + HexToPrefixedString(ipp.CR[5][:]), + HexToPrefixedString(ipp.CR[6][:]), + HexToPrefixedString(ipp.CR[7][:]), }, - FinalEvaluation: hex.EncodeToString(ipp.FinalEvaluation[:]), + FinalEvaluation: HexToPrefixedString(ipp.FinalEvaluation[:]), }) } @@ -70,29 +84,29 @@ func (ipp *IPAProof) UnmarshalJSON(data []byte) error { return err } - if len(aux.FinalEvaluation) != 64 { + if len(aux.FinalEvaluation) != 64 && len(aux.FinalEvaluation) != 66 { return fmt.Errorf("invalid hex string for final evaluation: %s", aux.FinalEvaluation) } - currentValueBytes, err := hex.DecodeString(aux.FinalEvaluation) + currentValueBytes, err := PrefixedHexStringToBytes(aux.FinalEvaluation) if err != nil { return fmt.Errorf("error decoding hex string for current value: %v", err) } copy(ipp.FinalEvaluation[:], currentValueBytes) for i := range ipp.CL { - if len(aux.CL[i]) != 64 { + if len(aux.CL[i]) != 64 && len(aux.CL[i]) != 66 { return fmt.Errorf("invalid hex string for CL[%d]: %s", i, aux.CL[i]) } - val, err := hex.DecodeString(aux.CL[i]) + val, err := PrefixedHexStringToBytes(aux.CL[i]) if err != nil { return fmt.Errorf("error decoding hex string for CL[%d]: %s", i, aux.CL[i]) } copy(ipp.CL[i][:], val) - if len(aux.CR[i]) != 64 { + if len(aux.CR[i]) != 64 && len(aux.CR[i]) != 66 { return fmt.Errorf("invalid hex string for CR[%d]: %s", i, aux.CR[i]) } - val, err = hex.DecodeString(aux.CR[i]) + val, err = PrefixedHexStringToBytes(aux.CR[i]) if err != nil { return fmt.Errorf("error decoding hex string for CR[%d]: %s", i, aux.CR[i]) } @@ -108,23 +122,23 @@ type verkleProofMarshaller struct { DepthExtensionPresent string `json:"depthExtensionPresent"` CommitmentsByPath []string `json:"commitmentsByPath"` D string `json:"d"` - IPAProof *IPAProof `json:"ipa_proof"` + IPAProof *IPAProof `json:"ipaProof"` } func (vp *VerkleProof) MarshalJSON() ([]byte, error) { aux := &verkleProofMarshaller{ OtherStems: make([]string, len(vp.OtherStems)), - DepthExtensionPresent: hex.EncodeToString(vp.DepthExtensionPresent), + DepthExtensionPresent: HexToPrefixedString(vp.DepthExtensionPresent), CommitmentsByPath: make([]string, len(vp.CommitmentsByPath)), - D: hex.EncodeToString(vp.D[:]), + D: HexToPrefixedString(vp.D[:]), IPAProof: vp.IPAProof, } for i, s := range vp.OtherStems { - aux.OtherStems[i] = hex.EncodeToString(s[:]) + aux.OtherStems[i] = HexToPrefixedString(s[:]) } for i, c := range vp.CommitmentsByPath { - aux.CommitmentsByPath[i] = hex.EncodeToString(c[:]) + aux.CommitmentsByPath[i] = HexToPrefixedString(c[:]) } return json.Marshal(aux) } @@ -136,21 +150,21 @@ func (vp *VerkleProof) UnmarshalJSON(data []byte) error { return err } - vp.DepthExtensionPresent, err = hex.DecodeString(aux.DepthExtensionPresent) + vp.DepthExtensionPresent, err = PrefixedHexStringToBytes(aux.DepthExtensionPresent) if err != nil { return fmt.Errorf("error decoding hex string for depth and extension present: %v", err) } vp.CommitmentsByPath = make([][32]byte, len(aux.CommitmentsByPath)) for i, c := range aux.CommitmentsByPath { - val, err := hex.DecodeString(c) + val, err := PrefixedHexStringToBytes(c) if err != nil { return fmt.Errorf("error decoding hex string for commitment #%d: %w", i, err) } copy(vp.CommitmentsByPath[i][:], val) } - currentValueBytes, err := hex.DecodeString(aux.D) + currentValueBytes, err := PrefixedHexStringToBytes(aux.D) if err != nil { return fmt.Errorf("error decoding hex string for D: %w", err) } @@ -158,7 +172,7 @@ func (vp *VerkleProof) UnmarshalJSON(data []byte) error { vp.OtherStems = make([][31]byte, len(aux.OtherStems)) for i, c := range aux.OtherStems { - val, err := hex.DecodeString(c) + val, err := PrefixedHexStringToBytes(c) if err != nil { return fmt.Errorf("error decoding hex string for other stem #%d: %w", i, err) } @@ -169,15 +183,45 @@ func (vp *VerkleProof) UnmarshalJSON(data []byte) error { return nil } +type stemStateDiffMarshaller struct { + Stem string `json:"stem"` + SuffixDiffs SuffixStateDiffs `json:"suffixDiffs"` +} + +func (ssd StemStateDiff) MarshalJSON() ([]byte, error) { + return json.Marshal(&stemStateDiffMarshaller{ + Stem: HexToPrefixedString(ssd.Stem[:]), + SuffixDiffs: ssd.SuffixDiffs, + }) +} + +func (ssd *StemStateDiff) UnmarshalJSON(data []byte) error { + var aux stemStateDiffMarshaller + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + stem, err := PrefixedHexStringToBytes(aux.Stem) + if err != nil { + return fmt.Errorf("invalid hex string for stem: %w", err) + } + *ssd = StemStateDiff{ + SuffixDiffs: aux.SuffixDiffs, + } + copy(ssd.Stem[:], stem) + return nil +} + type suffixStateDiffMarshaller struct { - Suffix byte `json:"suffix"` - CurrentValue string `json:"currentValue"` + Suffix byte `json:"suffix"` + CurrentValue *string `json:"currentValue"` } func (ssd SuffixStateDiff) MarshalJSON() ([]byte, error) { - var cvstr string + var cvstr *string if ssd.CurrentValue != nil { - cvstr = hex.EncodeToString(ssd.CurrentValue[:]) + tempstr := HexToPrefixedString(ssd.CurrentValue[:]) + cvstr = &tempstr } return json.Marshal(&suffixStateDiffMarshaller{ Suffix: ssd.Suffix, @@ -186,24 +230,22 @@ func (ssd SuffixStateDiff) MarshalJSON() ([]byte, error) { } func (ssd *SuffixStateDiff) UnmarshalJSON(data []byte) error { - aux := &suffixStateDiffMarshaller{ - CurrentValue: "", - } + aux := &suffixStateDiffMarshaller{} if err := json.Unmarshal(data, &aux); err != nil { return err } - if len(aux.CurrentValue) != 64 && len(aux.CurrentValue) != 0 { - return fmt.Errorf("invalid hex string for current value: %s", aux.CurrentValue) + if aux.CurrentValue != nil && len(*aux.CurrentValue) != 64 && len(*aux.CurrentValue) != 0 && len(*aux.CurrentValue) != 66 { + return fmt.Errorf("invalid hex string for current value: %s", *aux.CurrentValue) } *ssd = SuffixStateDiff{ Suffix: aux.Suffix, } - if len(aux.CurrentValue) != 0 { - currentValueBytes, err := hex.DecodeString(aux.CurrentValue) + if aux.CurrentValue != nil && len(*aux.CurrentValue) != 0 { + currentValueBytes, err := PrefixedHexStringToBytes(*aux.CurrentValue) if err != nil { return fmt.Errorf("error decoding hex string for current value: %v", err) } diff --git a/proof_test.go b/proof_test.go index 3262165c..fc69b103 100644 --- a/proof_test.go +++ b/proof_test.go @@ -425,7 +425,7 @@ func TestSuffixStateDiffJSONMarshalUn(t *testing.T) { }, } - expectedJSON := `{"suffix":65,"currentValue":"102030405060708090a0b0c0d0e0f000112233445566778899aabbccddeeff00"}` + expectedJSON := `{"suffix":65,"currentValue":"0x102030405060708090a0b0c0d0e0f000112233445566778899aabbccddeeff00"}` actualJSON, err := json.Marshal(ssd) if err != nil { t.Errorf("error marshalling SuffixStateDiff to JSON: %v", err) @@ -446,13 +446,52 @@ func TestSuffixStateDiffJSONMarshalUn(t *testing.T) { } } +func TestStemStateDiffJSONMarshalUn(t *testing.T) { + ssd := StemStateDiff{ + Stem: [31]byte{10}, + SuffixDiffs: []SuffixStateDiff{{ + Suffix: 0x41, + CurrentValue: &[32]byte{ + 0x10, 0x20, 0x30, 0x40, + 0x50, 0x60, 0x70, 0x80, + 0x90, 0xA0, 0xB0, 0xC0, + 0xD0, 0xE0, 0xF0, 0x00, + 0x11, 0x22, 0x33, 0x44, + 0x55, 0x66, 0x77, 0x88, + 0x99, 0xAA, 0xBB, 0xCC, + 0xDD, 0xEE, 0xFF, 0x00, + }, + }}, + } + + expectedJSON := `{"stem":"0x0a000000000000000000000000000000000000000000000000000000000000","suffixDiffs":[{"suffix":65,"currentValue":"0x102030405060708090a0b0c0d0e0f000112233445566778899aabbccddeeff00"}]}` + actualJSON, err := json.Marshal(ssd) + if err != nil { + t.Errorf("error marshalling SuffixStateDiff to JSON: %v", err) + } + + if string(actualJSON) != expectedJSON { + t.Errorf("JSON output doesn't match expected value.\nExpected: %s\nActual: %s", expectedJSON, string(actualJSON)) + } + + var actualSSD StemStateDiff + err = json.Unmarshal(actualJSON, &actualSSD) + if err != nil { + t.Errorf("error unmarshalling JSON to StemStateDiff: %v", err) + } + + if !reflect.DeepEqual(actualSSD, ssd) { + t.Errorf("SuffixStateDiff doesn't match expected value.\nExpected: %+v\nActual: %+v", ssd, actualSSD) + } +} + func TestSuffixStateDiffJSONMarshalUnCurrentValueNil(t *testing.T) { ssd := SuffixStateDiff{ Suffix: 0x41, CurrentValue: nil, } - expectedJSON := `{"suffix":65,"currentValue":""}` + expectedJSON := `{"suffix":65,"currentValue":null}` actualJSON, err := json.Marshal(ssd) if err != nil { t.Errorf("error marshalling SuffixStateDiff to JSON: %v", err)