From ac0743b688f5cfc15233f28c512b05682113f876 Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Tue, 10 Sep 2024 13:09:24 +0200 Subject: [PATCH 01/12] fix: guarantee that max prefix length is < min prefix length + child size --- go/ops.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/go/ops.go b/go/ops.go index 5ddecc1a..fffaa1a4 100644 --- a/go/ops.go +++ b/go/ops.go @@ -166,6 +166,10 @@ func (op *InnerOp) CheckAgainstSpec(spec *ProofSpec, b int) error { return errors.New("spec.InnerSpec.ChildSize must be >= 1") } + if spec.InnerSpec.MaxPrefixLength >= spec.InnerSpec.MinPrefixLength+spec.InnerSpec.ChildSize { + return errors.New("spec.InnerSpec.MaxPrefixLength must be < spec.InnerSpec.MinPrefixLength + spec.InnerSpec.ChildSize") + } + // ensures soundness, with suffix having to be of correct length if len(op.Suffix)%int(spec.InnerSpec.ChildSize) != 0 { return fmt.Errorf("InnerOp suffix malformed") From 3d7762d0b6d0723687c5b566ad1023dadc9fdac8 Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Tue, 10 Sep 2024 13:15:06 +0200 Subject: [PATCH 02/12] same fix for rust implementation --- rust/src/verify.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rust/src/verify.rs b/rust/src/verify.rs index ade0b93a..a70610f6 100644 --- a/rust/src/verify.rs +++ b/rust/src/verify.rs @@ -203,7 +203,11 @@ fn ensure_inner(inner: &ics23::InnerOp, spec: &ics23::ProofSpec) -> Result<()> { ); ensure!( inner_spec.child_size > 0, - "spec.InnerSpec.ChildSize must be >= 1" + "spec.inner_spec.child_size must be >= 1" + ); + ensure!( + inner_spec.max_prefix_length < inner_spec.min_prefix_length + inner_spec.child_size, + "spec.inner_spec.max_prefix_length must be < spec.inner_spec.min_prefix_length + spec.inner_spec.child_size" ); ensure!( inner.suffix.len() % (inner_spec.child_size as usize) == 0, From 98f872171e54e773570e85b14f9d123c75bead49 Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Tue, 10 Sep 2024 13:57:50 +0200 Subject: [PATCH 03/12] add test for rust --- rust/src/verify.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/rust/src/verify.rs b/rust/src/verify.rs index a70610f6..aedcf41b 100644 --- a/rust/src/verify.rs +++ b/rust/src/verify.rs @@ -488,6 +488,11 @@ mod tests { depth_limited_spec.min_depth = 2; depth_limited_spec.max_depth = 4; + + let mut max_prefix_length_too_large_spec = api::iavl_spec(); + let inner_spec = max_prefix_length_too_large_spec.inner_spec.as_mut().unwrap(); + inner_spec.max_prefix_length = 100; + let cases: HashMap<&'static str, ExistenceCase> = [ ( "empty proof fails", @@ -616,19 +621,32 @@ mod tests { proof: ExistenceProof { key: b"foo".to_vec(), value: b"bar".to_vec(), - leaf: Some(leaf), + leaf: Some(leaf.clone()), path: vec![ valid_inner.clone(), valid_inner.clone(), valid_inner.clone(), valid_inner.clone(), - valid_inner, + valid_inner.clone(), ], }, spec: depth_limited_spec, valid: false, }, ), + ( + "rejects inner spec with max prefix length >= min prefix lenght + child size", + ExistenceCase { + proof: ExistenceProof { + key: b"foo".to_vec(), + value: b"bar".to_vec(), + leaf: Some(leaf), + path: vec![valid_inner], + }, + spec: max_prefix_length_too_large_spec, + valid: false, + }, + ), ] .into_iter() .collect(); From 277f4f4aa6a140d664965363104e87cbb1b226a5 Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Wed, 18 Sep 2024 11:05:45 +0200 Subject: [PATCH 04/12] lint --- rust/src/verify.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rust/src/verify.rs b/rust/src/verify.rs index aedcf41b..f9df71fd 100644 --- a/rust/src/verify.rs +++ b/rust/src/verify.rs @@ -487,10 +487,12 @@ mod tests { let mut depth_limited_spec = api::iavl_spec(); depth_limited_spec.min_depth = 2; depth_limited_spec.max_depth = 4; - let mut max_prefix_length_too_large_spec = api::iavl_spec(); - let inner_spec = max_prefix_length_too_large_spec.inner_spec.as_mut().unwrap(); + let inner_spec = max_prefix_length_too_large_spec + .inner_spec + .as_mut() + .unwrap(); inner_spec.max_prefix_length = 100; let cases: HashMap<&'static str, ExistenceCase> = [ From 3ce23e4c576f2dba7939c35db064597efa8e5690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?colin=20axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Wed, 25 Sep 2024 12:12:48 +0200 Subject: [PATCH 05/12] Update rust/src/verify.rs --- rust/src/verify.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/src/verify.rs b/rust/src/verify.rs index f9df71fd..539f06a6 100644 --- a/rust/src/verify.rs +++ b/rust/src/verify.rs @@ -637,7 +637,7 @@ mod tests { }, ), ( - "rejects inner spec with max prefix length >= min prefix lenght + child size", + "rejects inner spec with max prefix length >= min prefix length + child size", ExistenceCase { proof: ExistenceProof { key: b"foo".to_vec(), From 5b14cd62f542f450127fab72f3a8dbfdce9a1d2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Colin=20Axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Wed, 25 Sep 2024 15:21:13 +0200 Subject: [PATCH 06/12] chore: rust formatting --- rust/src/verify.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/src/verify.rs b/rust/src/verify.rs index 539f06a6..b81e0335 100644 --- a/rust/src/verify.rs +++ b/rust/src/verify.rs @@ -487,7 +487,7 @@ mod tests { let mut depth_limited_spec = api::iavl_spec(); depth_limited_spec.min_depth = 2; depth_limited_spec.max_depth = 4; - + let mut max_prefix_length_too_large_spec = api::iavl_spec(); let inner_spec = max_prefix_length_too_large_spec .inner_spec From e77f94e7ddadfe6b7eb6ae9f364aa5122b550901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Colin=20Axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Wed, 25 Sep 2024 15:31:13 +0200 Subject: [PATCH 07/12] proto: add inner op max prefix length restriction comment --- go/proofs.pb.go | 3 ++- proto/cosmos/ics23/v1/proofs.proto | 3 ++- rust/src/cosmos.ics23.v1.rs | 1 + rust/src/proto_descriptor.bin | Bin 12545 -> 12627 bytes 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/go/proofs.pb.go b/go/proofs.pb.go index 1f64cb00..3cf74811 100644 --- a/go/proofs.pb.go +++ b/go/proofs.pb.go @@ -670,7 +670,8 @@ type InnerSpec struct { ChildOrder []int32 `protobuf:"varint,1,rep,packed,name=child_order,json=childOrder,proto3" json:"child_order,omitempty"` ChildSize int32 `protobuf:"varint,2,opt,name=child_size,json=childSize,proto3" json:"child_size,omitempty"` MinPrefixLength int32 `protobuf:"varint,3,opt,name=min_prefix_length,json=minPrefixLength,proto3" json:"min_prefix_length,omitempty"` - MaxPrefixLength int32 `protobuf:"varint,4,opt,name=max_prefix_length,json=maxPrefixLength,proto3" json:"max_prefix_length,omitempty"` + // the max prefix length must be less than the minimum prefix length + child size + MaxPrefixLength int32 `protobuf:"varint,4,opt,name=max_prefix_length,json=maxPrefixLength,proto3" json:"max_prefix_length,omitempty"` // empty child is the prehash image that is used when one child is nil (eg. 20 bytes of 0) EmptyChild []byte `protobuf:"bytes,5,opt,name=empty_child,json=emptyChild,proto3" json:"empty_child,omitempty"` // hash is the algorithm that must be used for each InnerOp diff --git a/proto/cosmos/ics23/v1/proofs.proto b/proto/cosmos/ics23/v1/proofs.proto index 7605aa1a..940f6802 100644 --- a/proto/cosmos/ics23/v1/proofs.proto +++ b/proto/cosmos/ics23/v1/proofs.proto @@ -190,7 +190,8 @@ message InnerSpec { repeated int32 child_order = 1; int32 child_size = 2; int32 min_prefix_length = 3; - int32 max_prefix_length = 4; + // the max prefix length must be less than the minimum prefix length + child size + int32 max_prefix_length = 4; // empty child is the prehash image that is used when one child is nil (eg. 20 bytes of 0) bytes empty_child = 5; // hash is the algorithm that must be used for each InnerOp diff --git a/rust/src/cosmos.ics23.v1.rs b/rust/src/cosmos.ics23.v1.rs index 43755969..6e78c086 100644 --- a/rust/src/cosmos.ics23.v1.rs +++ b/rust/src/cosmos.ics23.v1.rs @@ -180,6 +180,7 @@ pub struct InnerSpec { pub child_size: i32, #[prost(int32, tag = "3")] pub min_prefix_length: i32, + /// the max prefix length must be less than the minimum prefix length + child size #[prost(int32, tag = "4")] pub max_prefix_length: i32, /// empty child is the prehash image that is used when one child is nil (eg. 20 bytes of 0) diff --git a/rust/src/proto_descriptor.bin b/rust/src/proto_descriptor.bin index 34139d2dc76c7e68098d2e64b1d579b458f5fc83..3864941cd12c629197876af312d4336fe83acde1 100644 GIT binary patch delta 1370 zcmZ9LPfrt36vgMhzD}oaS^{IN&=wd##86PswHgy8#KdT#(Zq!b)e2205Ly!Artu57 z>CSIq*tj)Kbpa+&)P!Qf9}Sd1sPG9q@4cB!;_}yf&$;K`zIm73%!b5z_G9S1h<fkRQH#3eDQ6tJ&Vz79XQRwHJ4Enz5X6pi}`MUC1! z#8bg|#4%UN_*S`-dGcbFEJws(6JdtK*NF8i1qeBSdIz8>(Z1+cVZTO+ZXaR4MsA;& zfW76I>ty_>oMi5o*QI?M>@CZ1%nf4SDWo)DSZt6LSJN#yy-s{U7u7hR<0E?Olt}rA zadqnJ_oOIp22w&&ph|j$TZ~jW(c@_e+D)mwji^z_+#=&w<>$z)@)lWXWf+xN8pLiW z1qhb_>PrCO-ypl!ufo1T?vRhLZ_wDd=Y-;R;Dm)F`c5+GHraZ~!Kg~QP41{?C6jJT zD|uAmm^)&GNAOA7DZl*6()CBrf%O{LOz-gt+1W delta 1287 zcmYL|-Afc<6vpSg`|j+_JEOQ0ZRuK$njfT=)pbIUA`x^^6m*vm)rA=0{Se&;F?anJ z;Xe?|MxcRTUAPuh}lp{>54)mzmgbJU8=}b)L61ejO8JVx_M+&z0y({$y)m1`kSd5 z39FD$)X2M#DI^p%s$5d@S^@WZ+B@;!MxBJ0wG^SKliy_#iaPa_)e|LTDRNiI`H_B_ zc>H{oyhw58l5irqM#7{5AqP-t1E9+oT(k)LHL6^(2>UgP2h|+xy~tfB=Vv-IIh(Ar zeK+hq&xza(65CYJ8Zaz2$m`P6J)XWva@Z90IACNEy_;02TEw_b8XU@`C~oCaLQhwyWc#umuE^r{p zn)$E?6pvek_P}U`3?lcCoWDdWPz4#F^N_q)+XkGdMZ!F05S}m4Jar&?TjYn@6$sZB zmCYLsI8i&vwFGTD*K(pZ`Q4e857^GNB9ceBRA3vR`M(5$osaDQ1qkU8OP}Jz0n;&w zzP|Z*`Sr{6`^`tsfaWuzh!DpV^kh>Da5|>4`KO?&wU>LbyO3ezo{)2%oh3x)331i&- hG0t)+9|LHlgQ3308CB!ViuXT*cENr7ZfSYx&VRaHnd1Ne From f733e7530f99b41cf5b5ef38ce92ea05d41535f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Colin=20Axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Wed, 25 Sep 2024 16:04:32 +0200 Subject: [PATCH 08/12] test: add test for forging non existence proof --- go/ops_test.go | 113 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/go/ops_test.go b/go/ops_test.go index 7db46f20..82763250 100644 --- a/go/ops_test.go +++ b/go/ops_test.go @@ -3,6 +3,8 @@ package ics23 import ( "bytes" "encoding/hex" + "errors" + "fmt" "testing" ) @@ -61,3 +63,114 @@ func TestDoHash(t *testing.T) { }) } } + +func TestForgeNonexistenceProofWithIncorrectMaxPrefixLength(t *testing.T) { + spec := TendermintSpec + spec.InnerSpec.MaxPrefixLength = 33 + leafOp := spec.LeafSpec + aLeaf, _ := leafOp.Apply([]byte("a"), []byte("a")) + bLeaf, _ := leafOp.Apply([]byte("b"), []byte("b")) + b2Leaf, _ := leafOp.Apply([]byte("b2"), []byte("b2")) + + cLeaf, _ := leafOp.Apply([]byte("c"), []byte("c")) + aExist := ExistenceProof{ + Key: []byte("a"), + Value: []byte("a"), + Leaf: leafOp, + Path: []*InnerOp{ + &InnerOp{ + Hash: spec.InnerSpec.Hash, + Prefix: []byte{1}, + Suffix: append(bLeaf, b2Leaf...), + }, + &InnerOp{ + Hash: spec.InnerSpec.Hash, + Prefix: []byte{1}, + Suffix: cLeaf, + }, + }, + } + bExist := ExistenceProof{ + Key: []byte("b"), + Value: []byte("b"), + Leaf: leafOp, + Path: []*InnerOp{ + &InnerOp{ + Hash: spec.InnerSpec.Hash, + Prefix: append([]byte{1}, aLeaf...), + Suffix: b2Leaf, + }, + &InnerOp{ + Hash: spec.InnerSpec.Hash, + Prefix: []byte{1}, + Suffix: cLeaf, + }, + }, + } + b2Exist := ExistenceProof{ + Key: []byte("b2"), + Value: []byte("b2"), + Leaf: leafOp, + Path: []*InnerOp{ + &InnerOp{ + Hash: spec.InnerSpec.Hash, + Prefix: append(append([]byte{1}, aLeaf...), bLeaf...), + Suffix: []byte{}, + }, + &InnerOp{ + Hash: spec.InnerSpec.Hash, + Prefix: []byte{1}, + Suffix: cLeaf, + }, + }, + } + yHash, _ := aExist.Path[0].Apply(aLeaf) + cExist := ExistenceProof{ + Key: []byte("c"), + Value: []byte("c"), + Leaf: leafOp, + Path: []*InnerOp{ + &InnerOp{ + Hash: spec.InnerSpec.Hash, + Prefix: append([]byte{1}, yHash...), + Suffix: []byte{}, + }, + }, + } + aNotExist := NonExistenceProof{ + Key: []byte("a"), + Left: nil, + Right: &bExist, + } + root, err := aExist.Calculate() + if err != nil { + t.Fatal("failed to calculate existence proof of leaf a") + } + + expError := fmt.Errorf("inner, %w", errors.New("spec.InnerSpec.MaxPrefixLength must be < spec.InnerSpec.MinPrefixLength + spec.InnerSpec.ChildSize")) + err = aExist.Verify(spec, root, []byte("a"), []byte("a")) + if err.Error() != expError.Error() { + t.Fatal("attempting to prove existence of leaf a returned incorrect error") + } + + err = bExist.Verify(spec, root, []byte("b"), []byte("b")) + if err.Error() != expError.Error() { + t.Fatal("attempting to prove existence of leaf b returned incorrect error") + } + + err = b2Exist.Verify(spec, root, []byte("b2"), []byte("b2")) + if err.Error() != expError.Error() { + t.Fatal("attempting to prove existence of third leaf returned incorrect error") + } + + err = cExist.Verify(spec, root, []byte("c"), []byte("c")) + if err.Error() != expError.Error() { + t.Fatal("attempting to prove existence of leaf c returned incorrect error") + } + + err = aNotExist.Verify(spec, root, []byte("a")) + expError = fmt.Errorf("right proof, %w", expError) + if err.Error() != expError.Error() { + t.Fatal("attempting to prove non-existence of leaf a returned incorrect error") + } +} From 21a407ecbc5a8202c3fc464a44f8e50248b4f649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Colin=20Axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Wed, 25 Sep 2024 16:11:25 +0200 Subject: [PATCH 09/12] fix: test --- go/ops_test.go | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/go/ops_test.go b/go/ops_test.go index 82763250..440a08a1 100644 --- a/go/ops_test.go +++ b/go/ops_test.go @@ -64,8 +64,24 @@ func TestDoHash(t *testing.T) { } } -func TestForgeNonexistenceProofWithIncorrectMaxPrefixLength(t *testing.T) { - spec := TendermintSpec +func TestForgeNonExistenceProofWithIncorrectMaxPrefixLength(t *testing.T) { + spec := &ProofSpec{ // TendermintSpec + LeafSpec: &LeafOp{ + Prefix: []byte{0}, + PrehashKey: HashOp_NO_HASH, + Hash: HashOp_SHA256, + PrehashValue: HashOp_SHA256, + Length: LengthOp_VAR_PROTO, + }, + InnerSpec: &InnerSpec{ + ChildOrder: []int32{0, 1}, + MinPrefixLength: 1, + MaxPrefixLength: 1, + ChildSize: 32, // (no length byte) + Hash: HashOp_SHA256, + }, + } + spec.InnerSpec.MaxPrefixLength = 33 leafOp := spec.LeafSpec aLeaf, _ := leafOp.Apply([]byte("a"), []byte("a")) From b0cce5cd663ea82738fb879ef42f4fad1f777774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Colin=20Axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Wed, 25 Sep 2024 16:14:26 +0200 Subject: [PATCH 10/12] lint --- go/ops_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/go/ops_test.go b/go/ops_test.go index 440a08a1..46298833 100644 --- a/go/ops_test.go +++ b/go/ops_test.go @@ -94,12 +94,12 @@ func TestForgeNonExistenceProofWithIncorrectMaxPrefixLength(t *testing.T) { Value: []byte("a"), Leaf: leafOp, Path: []*InnerOp{ - &InnerOp{ + { Hash: spec.InnerSpec.Hash, Prefix: []byte{1}, Suffix: append(bLeaf, b2Leaf...), }, - &InnerOp{ + { Hash: spec.InnerSpec.Hash, Prefix: []byte{1}, Suffix: cLeaf, @@ -111,12 +111,12 @@ func TestForgeNonExistenceProofWithIncorrectMaxPrefixLength(t *testing.T) { Value: []byte("b"), Leaf: leafOp, Path: []*InnerOp{ - &InnerOp{ + { Hash: spec.InnerSpec.Hash, Prefix: append([]byte{1}, aLeaf...), Suffix: b2Leaf, }, - &InnerOp{ + { Hash: spec.InnerSpec.Hash, Prefix: []byte{1}, Suffix: cLeaf, @@ -128,12 +128,12 @@ func TestForgeNonExistenceProofWithIncorrectMaxPrefixLength(t *testing.T) { Value: []byte("b2"), Leaf: leafOp, Path: []*InnerOp{ - &InnerOp{ + { Hash: spec.InnerSpec.Hash, Prefix: append(append([]byte{1}, aLeaf...), bLeaf...), Suffix: []byte{}, }, - &InnerOp{ + { Hash: spec.InnerSpec.Hash, Prefix: []byte{1}, Suffix: cLeaf, @@ -146,7 +146,7 @@ func TestForgeNonExistenceProofWithIncorrectMaxPrefixLength(t *testing.T) { Value: []byte("c"), Leaf: leafOp, Path: []*InnerOp{ - &InnerOp{ + { Hash: spec.InnerSpec.Hash, Prefix: append([]byte{1}, yHash...), Suffix: []byte{}, From 7babca7fa81ec3a068a2bc4c97fd22f58c2b81c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Colin=20Axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Thu, 26 Sep 2024 10:32:08 +0200 Subject: [PATCH 11/12] test: add unit test for new restriction --- go/ops_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/go/ops_test.go b/go/ops_test.go index 22f4175d..e2d23912 100644 --- a/go/ops_test.go +++ b/go/ops_test.go @@ -150,6 +150,13 @@ func TestInnerOpCheckAgainstSpec(t *testing.T) { }, errors.New("spec.InnerSpec.ChildSize must be >= 1"), }, + { + "failure: MaxPrefixLength >= MinPrefixLength + ChildSize", + func() { + spec.InnerSpec.MaxPrefixLength = spec.InnerSpec.MinPrefixLength + spec.InnerSpec.ChildSize + }, + errors.New("spec.InnerSpec.MaxPrefixLength must be < spec.InnerSpec.MinPrefixLength + spec.InnerSpec.ChildSize"), + }, { "failure: inner op suffix malformed", func() { From 2b6f34f1f7142d48e172e19f934cb8203f52d949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Colin=20Axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Thu, 26 Sep 2024 15:49:58 +0200 Subject: [PATCH 12/12] chore: update changelog --- go/CHANGELOG.md | 1 + rust/CHANGELOG.md | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/go/CHANGELOG.md b/go/CHANGELOG.md index 4e16840d..d49eff71 100644 --- a/go/CHANGELOG.md +++ b/go/CHANGELOG.md @@ -3,6 +3,7 @@ # Unreleased - deps: bump golang to v1.22 ([#363](https://github.com/cosmos/ics23/pull/363)). +- fix: guarantee that `spec.InnerSpec.MaxPrefixLength` < `spec.InnerSpec.MinPrefixLength` + `spec.InnerSpec.ChildSize` ([#369](https://github.com/cosmos/ics23/pull/369)) # v0.11.0 diff --git a/rust/CHANGELOG.md b/rust/CHANGELOG.md index f384b40a..15e91aa7 100644 --- a/rust/CHANGELOG.md +++ b/rust/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +# Unreleased + +- fix: guarantee that `spec.InnerSpec.MaxPrefixLength` < `spec.InnerSpec.MinPrefixLength` + `spec.InnerSpec.ChildSize` ([#369](https://github.com/cosmos/ics23/pull/369)) + # v0.12.0 - chore(rust): Update `prost` to v0.13 ([#335](https://github.com/cosmos/ics23/pull/335), [#336](https://github.com/cosmos/ics23/pull/336))