From 3e35fadce265ccd3c07a8f137d5037b7b133db5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Colin=20Axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Wed, 10 Jan 2024 12:24:25 +0100 Subject: [PATCH 1/3] fix: change prehash value back to sha256 from noop Revert change to prehash value as this would require chains to upgrade the proof spec for their IBC clients. Add documentation to increase readability of the proof generation code and the root hash construction. Co-authored-by: Damian Nolan --- store/commit_info.go | 3 +++ store/proof.go | 52 +++++++++++++++++++++++++++++++++++++++----- store/proof_test.go | 12 +++++----- 3 files changed, 55 insertions(+), 12 deletions(-) diff --git a/store/commit_info.go b/store/commit_info.go index 4900c2f501de..e1b9d6c9152c 100644 --- a/store/commit_info.go +++ b/store/commit_info.go @@ -50,6 +50,9 @@ func (ci *CommitInfo) Hash() []byte { return rootHash } +// GetStoreProof takes in a storeKey and returns a proof of the store key in addition +// to the root hash it should be proved against. If an empty string is provided, the first +// store based on lexographical ordering will be proved. func (ci *CommitInfo) GetStoreProof(storeKey string) ([]byte, *CommitmentOp, error) { sort.Slice(ci.StoreInfos, func(i, j int) bool { return ci.StoreInfos[i].Name < ci.StoreInfos[j].Name diff --git a/store/proof.go b/store/proof.go index de46f5f317e7..c08ce3654fdc 100644 --- a/store/proof.go +++ b/store/proof.go @@ -24,7 +24,7 @@ var ( LeafSpec: &ics23.LeafOp{ Prefix: leafPrefix, PrehashKey: ics23.HashOp_NO_HASH, - PrehashValue: ics23.HashOp_NO_HASH, + PrehashValue: ics23.HashOp_SHA256, Hash: ics23.HashOp_SHA256, Length: ics23.LengthOp_VAR_PROTO, }, @@ -122,7 +122,34 @@ func (op CommitmentOp) Run(args [][]byte) ([][]byte, error) { return [][]byte{root}, nil } -// ProofFromByteSlices computes the proof from the given leaves. +// ProofFromByteSlices computes the proof from the given leaves. An iteration will be +// perfomed for each level of the tree, where each iteration hashes together the bottom most +// nodes. If the length of the bottom most nodes is odd, then the last node will be saved +// for the next iteration. +// +// Example: +// Iteration 1: +// n = 5 +// leaves = a, b, c, d, e. +// index = 2 (prove c) +// +// Iteration 2: +// n = 3 +// leaves = ab, cd, e +// index = 1 (prove c, so index of cd) +// +// Iteration 3: +// n = 2 +// leaves = abcd, e +// index = 0 (prove c, so index of abcd) +// +// Final iteration: +// n = 1 +// leaves = abcde +// index = 0 +// +// The bitwise & operator allows us to determine if the index or length is odd or even. +// The bitwise ^ operator allows us to increment when the value is even and decrement when it is odd. func ProofFromByteSlices(leaves [][]byte, index int) (rootHash []byte, inners []*ics23.InnerOp) { if len(leaves) == 0 { return emptyHash(), nil @@ -130,23 +157,35 @@ func ProofFromByteSlices(leaves [][]byte, index int) (rootHash []byte, inners [] n := len(leaves) for n > 1 { + // begin by constructing the proof for the inner node of the requested index + // a proof of the inner node is skipped only in the case where the requested index + // is the last element and it does not have a leaf pair (resulting in it being + // saved until the next iteration) if index < n-1 || index&1 == 1 { inner := &ics23.InnerOp{Hash: ics23.HashOp_SHA256} + // if proof index is even then child is from left, suffix is populated + // otherwise, child is from right and the prefix is populated. if index&1 == 0 { + // inner op(prefix=0x01 | child | suffix=leaves[index+1]) inner.Prefix = innerPrefix - inner.Suffix = leaves[index^1] + inner.Suffix = leaves[index^1] // XOR op is index+1 because index is even } else { - inner.Prefix = append(innerPrefix, leaves[index^1]...) + // inner op(prefix=0x01 | leaves[index-1] | child | suffix=nil) + inner.Prefix = append(innerPrefix, leaves[index^1]...) // XOR op is index-1 because index is odd } inners = append(inners, inner) } + + // hash together all leaf pairs for i := 0; i < n/2; i++ { leaves[i] = InnerHash(leaves[2*i], leaves[2*i+1]) } + + // save any leftover leaf for the next iteration if n&1 == 1 { leaves[n/2] = leaves[n-1] } - n = (n + 1) / 2 + n = (n + 1) / 2 // n + 1 accounts for any leaves which are added to the next iteration index /= 2 } @@ -178,7 +217,8 @@ func LeafHash(key, value []byte) ([]byte, error) { return SimpleMerkleSpec.LeafSpec.Apply(key, value) } -// InnerHash computes the hash of an inner node. +// InnerHash computes the hash of an inner node as defined by ics23: +// https://github.com/cosmos/ics23/blob/go/v0.10.0/proto/cosmos/ics23/v1/proofs.proto#L130 func InnerHash(left, right []byte) []byte { data := make([]byte, len(innerPrefix)+len(left)+len(right)) n := copy(data, innerPrefix) diff --git a/store/proof_test.go b/store/proof_test.go index b7a7d4019310..31cf0fbdf512 100644 --- a/store/proof_test.go +++ b/store/proof_test.go @@ -14,29 +14,29 @@ func TestProofFromBytesSlices(t *testing.T) { want string }{ {[]string{}, []string{}, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, - {[]string{"key1"}, []string{"value1"}, "09c468a07fe9bc1f14e754cff0acbad4faf9449449288be8e1d5d1199a247034"}, - {[]string{"key1"}, []string{"value2"}, "2131d85de3a8ded5d3a72bfc657f7324138540c520de7401ac8594785a3082fb"}, + {[]string{"key1"}, []string{"value1"}, "a44d3cc7daba1a4600b00a2434b30f8b970652169810d6dfa9fb1793a2189324"}, + {[]string{"key1"}, []string{"value2"}, "0638e99b3445caec9d95c05e1a3fc1487b4ddec6a952ff337080360b0dcc078c"}, // swap order with 2 keys { []string{"key1", "key2"}, []string{"value1", "value2"}, - "017788f37362dd0687beb59c0b3bfcc17a955120a4cb63dbdd4a0fdf9e07730e", + "8fd19b19e7bb3f2b3ee0574027d8a5a4cec370464ea2db2fbfa5c7d35bb0cff3", }, { []string{"key2", "key1"}, []string{"value2", "value1"}, - "ad2b0c23dbd3376440a5347fba02ff35cfad7930daa5e733930315b6fbb03b26", + "55d4bce1c53b7d394bd41bbfc2b239cc2e1c7e36423612a97181c47e79bb713c", }, // swap order with 3 keys { []string{"key1", "key2", "key3"}, []string{"value1", "value2", "value3"}, - "68f41a8a3508cb5f8eb3f1c7534a86fea9f59aa4898a5aac2f1bb92834ae2a36", + "1dd674ec6782a0d586a903c9c63326a41cbe56b3bba33ed6ff5b527af6efb3dc", }, { []string{"key1", "key3", "key2"}, []string{"value1", "value3", "value2"}, - "92cd50420c22d0c79f64dd1b04bfd5f5d73265f7ac37e65cf622f3cf8b963805", + "443382fbb629e0d50e86d6ea49e22aa4e27ba50262730b0122cec36860c903a2", }, } for i, tc := range tests { From 8582cee2e91f881bce0c969fdebfde4f79ad28c4 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Wed, 10 Jan 2024 12:23:50 -0500 Subject: [PATCH 2/3] Update store/proof.go --- store/proof.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/store/proof.go b/store/proof.go index c08ce3654fdc..e417dbb52825 100644 --- a/store/proof.go +++ b/store/proof.go @@ -163,7 +163,7 @@ func ProofFromByteSlices(leaves [][]byte, index int) (rootHash []byte, inners [] // saved until the next iteration) if index < n-1 || index&1 == 1 { inner := &ics23.InnerOp{Hash: ics23.HashOp_SHA256} - // if proof index is even then child is from left, suffix is populated + //Iif proof index is even then child is from left, suffix is populated // otherwise, child is from right and the prefix is populated. if index&1 == 0 { // inner op(prefix=0x01 | child | suffix=leaves[index+1]) From 519e599ea9390917fa817966cfbccaa1a371390f Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Wed, 10 Jan 2024 12:23:55 -0500 Subject: [PATCH 3/3] Update store/proof.go --- store/proof.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/store/proof.go b/store/proof.go index e417dbb52825..71b32d9729b7 100644 --- a/store/proof.go +++ b/store/proof.go @@ -157,10 +157,10 @@ func ProofFromByteSlices(leaves [][]byte, index int) (rootHash []byte, inners [] n := len(leaves) for n > 1 { - // begin by constructing the proof for the inner node of the requested index - // a proof of the inner node is skipped only in the case where the requested index + // Begin by constructing the proof for the inner node of the requested index. + // A proof of the inner node is skipped only in the case where the requested index // is the last element and it does not have a leaf pair (resulting in it being - // saved until the next iteration) + // saved until the next iteration). if index < n-1 || index&1 == 1 { inner := &ics23.InnerOp{Hash: ics23.HashOp_SHA256} //Iif proof index is even then child is from left, suffix is populated