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

fix absent keys in proofs #401

Merged
merged 5 commits into from
Oct 17, 2023
Merged

fix absent keys in proofs #401

merged 5 commits into from
Oct 17, 2023

Conversation

jsign
Copy link
Collaborator

@jsign jsign commented Oct 15, 2023

This PR proposes a fix to #400.

It fixes two things:

  1. Verifier fix: rebuilding the tree from a proof that has an absent key (other) case.
  2. Prover fix: leaf nodes shouldn't open C1/C2 for keys that don't match the leaf steam.

tree.go Outdated Show resolved Hide resolved
Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>
@jsign jsign force-pushed the jsign-test-case-fix branch from 68ebe55 to cff36e8 Compare October 16, 2023 14:28
jsign added 3 commits October 16, 2023 16:49
Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>
Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>
Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>
@@ -35,6 +35,7 @@ var (
errInsertIntoOtherStem = errors.New("insert splits a stem where it should not happen")
errUnknownNodeType = errors.New("unknown node type detected")
errMissingNodeInStateless = errors.New("trying to access a node that is missing from the stateless view")
errIsPOAStub = errors.New("trying to read/write a proof of absence leaf node")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created a new error that is returned whenever we try to read/write a leaf node that is only there to prove the absence of another stem.

This helps the new tests check that those operations fail for the expected reason.

@@ -485,6 +487,45 @@ func TestProofOfAbsenceOtherMultipleLeaves(t *testing.T) {
if len(proof.PoaStems) > 1 {
t.Fatalf("invalid number of proof-of-absence stems: %d", len(proof.PoaStems))
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gballet, pulled this extra twist in the test you did in the other PR. But did some changes that I'll describe in comments.

Comment on lines +523 to +528
if _, ok := node.children[ret3[4]].(*LeafNode); !ok {
t.Fatalf("expected leaf node at depth 5, got %v", node.children[ret3[4]])
}
if ln, ok := node.children[key[4]].(*LeafNode); !ok || !ln.isPOAStub {
t.Fatalf("expected unknown node at depth 5, got %v", node.children[key[4]])
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two changes compared to the original:

  • I fixed the t.Fatalf(...) ret3[X] and key[X] indices, that I later saw you also fixed in the PR. Just that I fixed it before you fixed it there, so that part should be fine here.
  • In L526, I do (*LeafNode) and not the original (*UnknownNode) for obvious reasons of this solution compared to the original one. I also check that this LeafNode has the isPOAStub turned on. Just to be sure since that should be true.

@@ -990,4 +1031,28 @@ func TestGenerateProofWithOnlyAbsentKeys(t *testing.T) {
if ok, err := VerifyVerkleProof(dproof, pe.Cis, pe.Zis, pe.Yis, cfg); !ok || err != nil {
t.Fatalf("reconstructed proof didn't verify: %v", err)
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added some extra logic to the original test in #400.

Comment on lines +1035 to +1046
// Double-check that if we try to access any key in 40000000000000000000000000000000000000000000000000000000000000{XX}
// in the reconstructed tree, we get an error. This LeafNode is only supposed to prove
// the absence of 40100000000000000000000000000000000000000000000000000000000000{YY}, so
// we don't know anything about any value for slots XX.
for i := 0; i < common.VectorLength; i++ {
var key [32]byte
copy(key[:], presentKey)
key[31] = byte(i)
if _, err := droot.Get(key[:], nil); err != errIsPOAStub {
t.Fatalf("expected ErrPOALeafValue, got %v", err)
}
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double-check that read access to any key of the proof of absence leaf node must fail with the expected error.

Comment on lines +1404 to +1413
// Note that keys might contain keys that don't correspond to this leaf node.
// We should only analize the inclusion of C1/C2 for keys corresponding to this
// leaf node stem.
if equalPaths(n.stem, key) {
hasC1 = hasC1 || (key[31] < 128)
hasC2 = hasC2 || (key[31] >= 128)
if hasC2 {
break
}
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing new here, this is the original fix.

tree.go Outdated
Comment on lines 388 to 396
if equalPaths(child.stem, stem) {
// We can't insert anything into a POA leaf node.
if child.isPOAStub {
return errIsPOAStub
}
n.cowChild(nChild)
return child.insertMultiple(stem, values)
}
n.cowChild(nChild)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interestingly, (n *InsertNode) Insert(...) internally calls InsertStem(...) (and not (*LeafNode) Insert(...), so we short-circuit here.

Note that unfortunately I had to put n.cowChild(...) in two places, to avoid an unwanted side effect of doing that and doing return errIsPOAStub later, which would be incorrect. (i.e: unwanted side-effect).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure what side effect you mean, but in any case please add a comment so that we know later on why the n.cowChild ended up here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point is that we need to return before calling n.cowChild(..).

If we do n.cowChild(...) (saying that this children as changed), and return an error later, it would be incorrect to mark that children as changed.

Not sure if that deserves a comment. It's just something that any API that fails should be aware of (i.e: assuming it will succeed doing stuff, and fail later without reverting the effects).

Comment on lines +540 to +544
// We can't return the values since it's a POA leaf node, so we know nothing
// about its values.
if child.isPOAStub {
return nil, errIsPOAStub
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other important short-circuit. (*InternalNode) Get(..) internally calls (*InternalNode) GetStem(...) to get all the values and index later.

Comment on lines +1419 to 1424
// If this tree is a full tree (i.e: not a stateless tree), we know we have c1 and c2 values.
// Also, we _need_ them independently of hasC1 or hasC2 since the prover needs `Fis`.
if !n.isPOAStub {
if err := banderwagon.BatchMapToScalarField([]*Fr{&poly[2], &poly[3]}, []*Point{n.c1, n.c2}); err != nil {
return nil, nil, nil, fmt.Errorf("batch mapping to scalar fields: %s", err)
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this leaf node isn't a stub, we do the original thing that I removed from above. Nothing weird, since we are in a non-stateless tree.

Comment on lines +1425 to +1430
} else if hasC1 || hasC2 || n.c1 != nil || n.c2 != nil {
// This LeafNode is a proof of absence stub. It must be true that
// both c1 and c2 are nil, and that hasC1 and hasC2 are false.
// Let's just check that to be sure, since the code below can't use
// poly[2] or poly[3].
return nil, nil, nil, fmt.Errorf("invalid proof of absence stub")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the LeafNode is a stub, let's be defensive and check some invariants:

  • We can't conclude that hasC1 is true.
  • Same with hasC2.
  • Any LeafNode with isPOAStub was constructed with nil c1 and c2. So check that.

This is important since lines below L1433 and L1439 will try to use the poly[2] and poly[3] values. Which in this case aren't calculated (because we can't!).

Maybe this check that I did is very paranoid; since indirectly those branches below have the "right" if cases. But my check is stronger: I don't care what the hasC1 or hasC2 evaluation was; those can never be ON in a stub. Period.

@jsign jsign marked this pull request as ready for review October 16, 2023 20:24
@jsign jsign requested a review from gballet October 16, 2023 20:25
Copy link
Member

@gballet gballet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM overall, but left a few questions and comments

tree.go Outdated
Comment on lines 388 to 396
if equalPaths(child.stem, stem) {
// We can't insert anything into a POA leaf node.
if child.isPOAStub {
return errIsPOAStub
}
n.cowChild(nChild)
return child.insertMultiple(stem, values)
}
n.cowChild(nChild)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure what side effect you mean, but in any case please add a comment so that we know later on why the n.cowChild ended up here.

tree.go Outdated
Comment on lines 389 to 393
// We can't insert anything into a POA leaf node.
if child.isPOAStub {
return errIsPOAStub
}
n.cowChild(nChild)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite like this comment, because it sounds like you can not insert in ANY type of PoA LeafNode, and the fact is that you can. I would rephrase as "you can't update leaf values of a PoA stem" or something like taht.

Copy link
Collaborator Author

@jsign jsign Oct 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because it sounds like you can not insert in ANY type of PoA LeafNode, and the fact is that you can

You can't. In today's definition of isPOAStub, the only case is true is for a LeafNode that is only there to prove the absence of a stem. In this case, you can't insert any value. (i.e: you don't have C1 nor C2, thus you can't update things)

Maybe you're thinking about potential future "stub" cases where leaf nodes are partially filled and you might be able to insert things?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, the way you can is precisely when you are inserting a new leaf that has a common prefix with the PoA stem. So you can not insert values into it, but you can insert a LeafNode into it (by producing intermediate InternalNodes).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, but my comment is inside equalPaths(child.stem, stem) so I'm not talking about that case, but inserting values.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In any case, I can add insert values in the comment if that helps.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment on lines -1371 to -1373
if err := banderwagon.BatchMapToScalarField([]*Fr{&poly[2], &poly[3]}, []*Point{n.c1, n.c2}); err != nil {
return nil, nil, nil, fmt.Errorf("batch mapping to scalar fields: %s", err)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, in 402 I catch this even at the call site in (*InternalNode) so that I don't have to come here. What I don't see, is why you need to move this code instead of just using an if like you do anyway line 1421 ?

Comment on lines -1371 to -1373
if err := banderwagon.BatchMapToScalarField([]*Fr{&poly[2], &poly[3]}, []*Point{n.c1, n.c2}); err != nil {
return nil, nil, nil, fmt.Errorf("batch mapping to scalar fields: %s", err)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can still check the invariant later, but apart from that I don't quite see what that changes 🤔 ?

@jsign jsign requested a review from gballet October 17, 2023 11:25
Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>
Copy link
Member

@gballet gballet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, I don't quite like the added nesting in (*LeafNode).GetProofItems but we've spent enough time on this. Merging.

@gballet gballet merged commit c4f5874 into jsign-test-case Oct 17, 2023
1 check passed
@jsign jsign deleted the jsign-test-case-fix branch October 17, 2023 12:35
jsign added a commit that referenced this pull request Oct 17, 2023
* test: gen proof with only absent keys

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* review feedback

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* fix absent keys in proofs (#401)

* fix absent keys in proofs

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* add POA stub marker

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* defensive case

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* fixes

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* improving comment

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

---------

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

---------

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants