Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proof of absence and presence for the same path with nil proof of presence value #414

Merged
merged 5 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 26 additions & 20 deletions proof_ipa.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,16 @@ func PreStateTreeFromProof(proof *Proof, rootC *Point) (VerkleNode, error) { //
return nil, fmt.Errorf("proof of absence stems are not sorted")
}

// We build a cache of stems that have a presence extension status.
stemsWithExtPresent := map[string]struct{}{}
i := 0
for _, es := range proof.ExtStatus {
if es&3 == extStatusPresent {
stemsWithExtPresent[string(stems[i])] = struct{}{}
}
i++
}

// assign one or more stem to each stem info
for _, es := range proof.ExtStatus {
depth := es >> 3
Expand All @@ -432,8 +442,14 @@ func PreStateTreeFromProof(proof *Proof, rootC *Point) (VerkleNode, error) { //
}
}
case extStatusAbsentOther:
si.stem = poas[0]
poas = poas[1:]
// For this absent path, we must first check if this path contains a proof of presence.
// If that is the case, we don't have to do anything since the corresponding leaf will be
// constructed by that extension status (already processed or to be processed).
// In other case, we should get the stem from the list of proof of absence stems.
if _, ok := stemsWithExtPresent[string(path)]; !ok {
si.stem = poas[0]
poas = poas[1:]
}
jsign marked this conversation as resolved.
Show resolved Hide resolved
// All keys that are part of a proof of absence, must contain empty
// prestate values. If that isn't the case, the proof is invalid.
for i, k := range proof.Keys { // TODO: DoS risk, use map or binary search.
Expand All @@ -443,36 +459,23 @@ func PreStateTreeFromProof(proof *Proof, rootC *Point) (VerkleNode, error) { //
}
}
}
default:
// the first stem could be missing (e.g. the second stem in the
// group is the one that is present. Compare each key to the first
// stem, along the length of the path only.
stemPath := stems[stemIndex][:len(path)]
case extStatusPresent:
jsign marked this conversation as resolved.
Show resolved Hide resolved
si.values = map[byte][]byte{}
si.stem = stems[stemIndex]
jsign marked this conversation as resolved.
Show resolved Hide resolved
for i, k := range proof.Keys { // TODO: DoS risk, use map or binary search.
if bytes.Equal(k[:len(path)], stemPath) && proof.PreValues[i] != nil {
if bytes.Equal(k[:31], si.stem) {
jsign marked this conversation as resolved.
Show resolved Hide resolved
si.values[k[31]] = proof.PreValues[i]
si.has_c1 = si.has_c1 || (k[31] < 128)
si.has_c2 = si.has_c2 || (k[31] >= 128)
// This key has values, its stem is the one that
// is present.
if si.stem == nil {
si.stem = k[:31]
continue
}
// Any other key with values must have the same
// same previously detected stem. If that isn't the case,
// the proof is invalid.
if !bytes.Equal(si.stem, k[:31]) {
return nil, fmt.Errorf("multiple keys with values found for stem %x", k[:31])
}
jsign marked this conversation as resolved.
Show resolved Hide resolved
}
}
// For a proof of presence, we must always have detected a stem.
// If that isn't the case, the proof is invalid.
if si.stem == nil {
return nil, fmt.Errorf("no stem found for path %x", path)
}
default:
return nil, fmt.Errorf("invalid extension status: %d", si.stemType)
}
info[string(path)] = si
paths = append(paths, path)
Expand All @@ -488,6 +491,9 @@ func PreStateTreeFromProof(proof *Proof, rootC *Point) (VerkleNode, error) { //
}
}
}
if stemIndex != len(stems) {
return nil, fmt.Errorf("not all stems were used: %d", len(stems))
}
jsign marked this conversation as resolved.
Show resolved Hide resolved

if len(poas) != 0 {
return nil, fmt.Errorf("not all proof of absence stems were used: %d", len(poas))
Expand Down
53 changes: 53 additions & 0 deletions proof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1125,3 +1125,56 @@ func TestProofOfPresenceWithEmptyValue(t *testing.T) {
t.Fatal("differing commitment for child #0")
}
}

func TestDoubleProofOfAbsence(t *testing.T) {
jsign marked this conversation as resolved.
Show resolved Hide resolved
root := New()

// Insert some keys.
key11, _ := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000001")
key12, _ := hex.DecodeString("0003000000000000000000000000000000000000000000000000000000000001")

if err := root.Insert(key11, fourtyKeyTest, nil); err != nil {
t.Fatalf("could not insert key: %v", err)
}
if err := root.Insert(key12, fourtyKeyTest, nil); err != nil {
t.Fatalf("could not insert key: %v", err)
}

// Try to prove to different stems that end up in the same LeafNode without any other proof of presence
// in that leaf node. i.e: two proof of absence in the same leaf node with no proof of absence.
jsign marked this conversation as resolved.
Show resolved Hide resolved
key2, _ := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000100")
key3, _ := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000200")
proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, keylist{key2, key3}, nil)

serialized, statediff, err := SerializeProof(proof)
if err != nil {
t.Fatalf("could not serialize proof: %v", err)
}

dproof, err := DeserializeProof(serialized, statediff)
if err != nil {
t.Fatalf("error deserializing proof: %v", err)
}

droot, err := PreStateTreeFromProof(dproof, root.Commit())
if err != nil {
t.Fatal(err)
}

if !droot.Commit().Equal(root.Commit()) {
t.Fatal("differing root commitments")
}

// Depite we have two proof of absences for different steams, we should only have one
// stem in `others`. i.e: we only need one for both steams.
if len(proof.PoaStems) != 1 {
t.Fatalf("invalid number of proof-of-absence stems: %d", len(proof.PoaStems))
}

// Despite we have two steams proved absent, we only need one extension status.
// A single extension status can already regenerate this branch, so the second stem
// can identify this branch was recreated.
if len(proof.ExtStatus) != 1 {
t.Fatalf("invalid number of proof-of-absence stems: %d", len(proof.PoaStems))
}
jsign marked this conversation as resolved.
Show resolved Hide resolved
}
5 changes: 3 additions & 2 deletions tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -1488,10 +1488,11 @@ func (n *LeafNode) GetProofItems(keys keylist, _ NodeResolverFn) (*ProofElements
// corner case (see previous corner case): if a proof-of-absence
// stem was found, and it now turns out the same stem is used as
// a proof of presence, clear the proof-of-absence list to avoid
// redundancy.
// redundancy. Note that we don't delete the extension statuses
// since that is needed to figure out which is the correct
// stem for this path.
if len(poass) > 0 {
poass = nil
esses = nil
jsign marked this conversation as resolved.
Show resolved Hide resolved
}

var (
Expand Down
4 changes: 2 additions & 2 deletions tree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1050,8 +1050,8 @@ func TestGetProofItemsNoPoaIfStemPresent(t *testing.T) {
if len(poas) != 0 {
t.Fatalf("returned %d poas instead of 0", len(poas))
}
if len(esses) != 1 {
t.Fatalf("returned %d extension statuses instead of the expected 1", len(esses))
if len(esses) != 2 {
t.Fatalf("returned %d extension statuses instead of the expected 2", len(esses))
jsign marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down