From b2310f168405504dd659ee6e03c03599f0f2381d Mon Sep 17 00:00:00 2001 From: Martin Hutchinson Date: Thu, 21 Aug 2025 15:13:48 +0000 Subject: [PATCH] [VIndex] Verify inclusion proofs in the index --- go.mod | 2 +- go.sum | 4 ++-- vindex/README.md | 2 +- vindex/api/api.go | 11 +++++++++-- vindex/cmd/client/client.go | 32 ++++++++++++++++++++++++++------ vindex/map.go | 17 +++++++++++++++-- 6 files changed, 54 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 9573c6c..2ac2627 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/transparency-dev/incubator go 1.24.1 require ( - filippo.io/torchwood v0.5.1-0.20250821112833-13ed7b69c1e2 + filippo.io/torchwood v0.5.1-0.20250821141945-7cf4555d7644 github.com/cockroachdb/pebble v1.1.5 github.com/google/go-cmp v0.7.0 github.com/gorilla/mux v1.8.1 diff --git a/go.sum b/go.sum index 8822ebb..1ae3c78 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -filippo.io/torchwood v0.5.1-0.20250821112833-13ed7b69c1e2 h1:H4ha4iGbm5VltuTnAG9MIfiScJZJiK/db+w2uLVOjbU= -filippo.io/torchwood v0.5.1-0.20250821112833-13ed7b69c1e2/go.mod h1:Z+iz3Syg0RCaVkL9nBjG2STp/9HpuFl1+SbaNSZ/Ez8= +filippo.io/torchwood v0.5.1-0.20250821141945-7cf4555d7644 h1:xPQ8RTWOsXdseR4XG7RRuERLvJOBcbjMohcZ66Nj2AY= +filippo.io/torchwood v0.5.1-0.20250821141945-7cf4555d7644/go.mod h1:Z+iz3Syg0RCaVkL9nBjG2STp/9HpuFl1+SbaNSZ/Ez8= github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= diff --git a/vindex/README.md b/vindex/README.md index a6e1bc4..a18ea07 100644 --- a/vindex/README.md +++ b/vindex/README.md @@ -197,7 +197,7 @@ Known applications: | 3 | Incremental update | ✅ | | 4 | Verify that mapped data matches Input Log Checkpoint | ✅ | | 5 | Output log | ✅ | -| 6 | Proofs served on Lookup | ❌ | +| 6 | Proofs served on Lookup | ✅ | | 7 | Storage backed verifiable-map | ❌ | | 8 | MapFn defined in WASM | ❌ | | 9 | Support reading directly from Input Log instead of Clone | ✅ | diff --git a/vindex/api/api.go b/vindex/api/api.go index 12b087d..4defd26 100644 --- a/vindex/api/api.go +++ b/vindex/api/api.go @@ -44,8 +44,15 @@ type LookupResponse struct { // These values represent the lookup operation in the index at the root hash // committed to by OutputLogLeaf. The values contain all indices for the given // key, and the proof binds these values at this key at the index root hash. - IndexValue []uint64 `json:"index_value"` - IndexProof [][sha256.Size]byte `json:"index_proof"` + IndexValue []uint64 `json:"index_value"` + IndexProof []IndexNode `json:"index_proof"` +} + +// IndexNode is a node in the Verifiable Index. +type IndexNode struct { + LabelBitLen uint32 `json:"label_bit_len"` + LabelPath []byte `json:"label_path"` + Hash [sha256.Size]byte `json:"hash"` } // OutputLogLeaf describes a leaf in the output log. diff --git a/vindex/cmd/client/client.go b/vindex/cmd/client/client.go index 3becc97..d7deff8 100644 --- a/vindex/cmd/client/client.go +++ b/vindex/cmd/client/client.go @@ -32,6 +32,7 @@ import ( "net/url" "slices" + "filippo.io/torchwood/prefix" "github.com/transparency-dev/formats/log" "github.com/transparency-dev/incubator/vindex/api" "github.com/transparency-dev/merkle/proof" @@ -141,7 +142,8 @@ type VIndexClient struct { // TODO(mhutchinson): maybe this should return the Input Log Checkpoint that was committed to in // the Output Log leaf? func (c VIndexClient) Lookup(ctx context.Context, key string) ([]uint64, error) { - resp, err := c.lookupUnverified(ctx, key) + kh := sha256.Sum256([]byte(key)) + resp, err := c.lookupUnverified(ctx, kh) if err != nil { return nil, fmt.Errorf("lookup for %q failed: %v", *lookup, err) } @@ -182,18 +184,36 @@ func (c VIndexClient) Lookup(ctx context.Context, key string) ([]uint64, error) } } vindexLeafHash := idxLeafHash.Sum(nil) - vindexKeyHash := sha256.Sum256([]byte(key)) - // TODO(mhutchinson): verify inclusion in the vindex! - klog.Warningf("TODO: confirm inclusion of leaf hash %x at key location %x with root hash %x", vindexLeafHash, vindexKeyHash, mapRoot) + + pns := make([]prefix.ProofNode, len(resp.IndexProof)) + for i, p := range resp.IndexProof { + label, err := prefix.NewLabel(p.LabelBitLen, p.LabelPath) + if err != nil { + return nil, fmt.Errorf("failed to create label: %v", err) + } + pns[i] = prefix.ProofNode{ + Label: label, + Hash: p.Hash, + } + } + + if len(resp.IndexValue) > 0 { + if err := prefix.VerifyMembershipProof(sha256.Sum256, kh, [32]byte(vindexLeafHash), pns, [32]byte(mapRoot)); err != nil { + return nil, fmt.Errorf("failed to verify membership: %v", err) + } + } else { + if err := prefix.VerifyNonMembershipProof(sha256.Sum256, kh, pns, [32]byte(mapRoot)); err != nil { + return nil, fmt.Errorf("failed to verify non-membership: %v", err) + } + } return resp.IndexValue, nil } -func (c VIndexClient) lookupUnverified(ctx context.Context, key string) (api.LookupResponse, error) { +func (c VIndexClient) lookupUnverified(ctx context.Context, kh [sha256.Size]byte) (api.LookupResponse, error) { var lookupResp api.LookupResponse // For now, keys are stored under the hash of the key - kh := sha256.Sum256([]byte(key)) u := c.lookupURL.JoinPath(hex.EncodeToString(kh[:])) req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) diff --git a/vindex/map.go b/vindex/map.go index 2acde72..446301a 100644 --- a/vindex/map.go +++ b/vindex/map.go @@ -396,8 +396,21 @@ func (b *VerifiableIndex) Lookup(ctx context.Context, key [sha256.Size]byte) (ap } result.IndexValue = allIndices - // TODO(filosottile): Generate proof for the vindex - result.IndexProof = nil + found, viProof, err := b.vindex.Lookup(ctx, key) + if err != nil { + return result, fmt.Errorf("failed to get inclusion proof from vindex: %v", err) + } + if expectFound := len(allIndices) > 0; expectFound != found { + return result, fmt.Errorf("found = %t, but expected %t (number of indices: %d)", found, expectFound, len(allIndices)) + } + result.IndexProof = make([]api.IndexNode, len(viProof)) + for i, p := range viProof { + result.IndexProof[i] = api.IndexNode{ + LabelBitLen: p.Label.BitLen(), + LabelPath: p.Label.Bytes(), + Hash: p.Hash, + } + } return result, nil }