Skip to content

Commit

Permalink
proof: absent key proof doesn't verify (#400)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
jsign authored Oct 17, 2023
1 parent 0a4e93e commit aae823f
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 9 deletions.
1 change: 1 addition & 0 deletions doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
)

const (
Expand Down
116 changes: 115 additions & 1 deletion proof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import (
"fmt"
"reflect"
"testing"

"github.com/crate-crypto/go-ipa/common"
)

func TestProofVerifyTwoLeaves(t *testing.T) {
Expand Down Expand Up @@ -472,7 +474,7 @@ func TestProofOfAbsenceOtherMultipleLeaves(t *testing.T) {
if err := root.Insert(key, testValue, nil); err != nil {
t.Fatalf("could not insert key: %v", err)
}
root.Commit()
rootC := root.Commit()

ret1, _ := hex.DecodeString("0303030303030303030303030303030303030303030303030303030303030300")
ret2, _ := hex.DecodeString("0303030303030303030303030303030303030303030303030303030303030301")
Expand All @@ -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))
}

deserialized, err := PreStateTreeFromProof(proof, rootC)
if err != nil {
t.Fatalf("error deserializing %v", err)
}

got, err := deserialized.Get(ret1, nil)
if err != nil {
t.Fatalf("error while trying to read missing value: %v", err)
}
if got != nil {
t.Fatalf("should have returned nil, got: %v", got)
}

// simulate the execution of a tx that creates a leaf at an address that isn't the one that is
// proven for absence, but needs to be inserted in the proof-of-absence stem.
// It differs from the poa stem here: 🠃
ret3, _ := hex.DecodeString("0303030304030303030303030303030303030303030303030303030303030300")
err = deserialized.Insert(ret3, testValue, nil)
if err != nil {
t.Fatalf("error inserting value in proof-of-asbsence stem: %v", err)
}

// check that there are splits up to depth 4
node := deserialized.(*InternalNode)
for node.depth < 4 {
child, ok := node.children[ret3[node.depth]].(*InternalNode)
if !ok {
t.Fatalf("expected Internal node at depth %d, trie = %s", node.depth, ToDot(deserialized))
}
node = child
}

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]])
}
}

func TestProofOfAbsenceNoneMultipleStems(t *testing.T) {
Expand Down Expand Up @@ -942,3 +983,76 @@ func TestProofOfAbsenceBorderCase(t *testing.T) {
t.Fatal("differing commitment for child #0")
}
}

func TestGenerateProofWithOnlyAbsentKeys(t *testing.T) {
t.Parallel()

// Create a tree with only one key.
root := New()
presentKey, _ := hex.DecodeString("4000000000000000000000000000000000000000000000000000000000000000")
if err := root.Insert(presentKey, zeroKeyTest, nil); err != nil {
t.Fatalf("inserting into the original failed: %v", err)
}
root.Commit()

// Create a proof with a key with the same first byte, but different second byte (i.e: absent).
absentKey, _ := hex.DecodeString("4010000000000000000000000000000000000000000000000000000000000000")
proof, cis, zis, yis, err := MakeVerkleMultiProof(root, nil, keylist{absentKey}, nil)
if err != nil {
t.Fatal(err)
}

// It must pass.
if ok, err := VerifyVerkleProof(proof, cis, zis, yis, cfg); !ok || err != nil {
t.Fatalf("original proof didn't verify: %v", err)
}

// Serialize + Deserialize + build tree from proof.
vp, statediff, err := SerializeProof(proof)
if err != nil {
t.Fatal(err)
}
dproof, err := DeserializeProof(vp, statediff)
if err != nil {
t.Fatal(err)
}
droot, err := PreStateTreeFromProof(dproof, root.Commit())
if err != nil {
t.Fatal(err)
}

// From the rebuilt tree, validate the proof.
pe, _, _, err := GetCommitmentsForMultiproof(droot, keylist{absentKey}, nil)
if err != nil {
t.Fatal(err)
}

// It must pass.
if ok, err := VerifyVerkleProof(dproof, pe.Cis, pe.Zis, pe.Yis, cfg); !ok || err != nil {
t.Fatalf("reconstructed proof didn't verify: %v", err)
}

// 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)
}
}

// The same applies to trying to insert values in this LeafNode, this shouldn't be allowed since we don't know
// anything about C1 or C2 to do a proper updating.
for i := 0; i < common.VectorLength; i++ {
var key [32]byte
copy(key[:], presentKey)
key[31] = byte(i)
if err := droot.Insert(key[:], zeroKeyTest, nil); err != errIsPOAStub {
t.Fatalf("expected ErrPOALeafValue, got %v", err)
}
}
}
65 changes: 57 additions & 8 deletions tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,11 @@ type (
c1, c2 *Point

depth byte

// IsPOAStub indicates if this LeafNode is a proof of absence
// for a steam that isn't present in the tree. This flag is only
// true in the context of a stateless tree.
isPOAStub bool
}
)

Expand Down Expand Up @@ -380,10 +385,15 @@ func (n *InternalNode) InsertStem(stem []byte, values [][]byte, resolver NodeRes
// splits.
return n.InsertStem(stem, values, resolver)
case *LeafNode:
n.cowChild(nChild)
if equalPaths(child.stem, stem) {
// We can't insert any values into a POA leaf node.
if child.isPOAStub {
return errIsPOAStub
}
n.cowChild(nChild)
return child.insertMultiple(stem, values)
}
n.cowChild(nChild)

// A new branch node has to be inserted. Depending
// on the next word in both keys, a recursion into
Expand Down Expand Up @@ -439,6 +449,15 @@ func (n *InternalNode) CreatePath(path []byte, stemInfo stemInfo, comms []*Point
n.children[path[0]] = Empty{}
case extStatusAbsentOther:
// insert poa stem
newchild := &LeafNode{
commitment: comms[0],
stem: stemInfo.stem,
values: nil,
depth: n.depth + 1,
isPOAStub: true,
}
n.children[path[0]] = newchild
comms = comms[1:]
case extStatusPresent:
// insert stem
newchild := &LeafNode{
Expand Down Expand Up @@ -518,6 +537,11 @@ func (n *InternalNode) GetStem(stem []byte, resolver NodeResolverFn) ([][]byte,
return n.GetStem(stem, resolver)
case *LeafNode:
if equalPaths(child.stem, stem) {
// 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
}
return child.values, nil
}
return nil, nil
Expand Down Expand Up @@ -1014,6 +1038,10 @@ func (n *InternalNode) touchCoW(index byte) {
}

func (n *LeafNode) Insert(key []byte, value []byte, _ NodeResolverFn) error {
if n.isPOAStub {
return errIsPOAStub
}

if len(key) != StemSize+1 {
return fmt.Errorf("invalid key size: %d", len(key))
}
Expand Down Expand Up @@ -1268,6 +1296,10 @@ func (n *LeafNode) Delete(k []byte, _ NodeResolverFn) (bool, error) {
}

func (n *LeafNode) Get(k []byte, _ NodeResolverFn) ([]byte, error) {
if n.isPOAStub {
return nil, errIsPOAStub
}

if !equalPaths(k, n.stem) {
// If keys differ, return nil in order to
// signal that the key isn't present in the
Expand Down Expand Up @@ -1368,19 +1400,36 @@ func (n *LeafNode) GetProofItems(keys keylist, _ NodeResolverFn) (*ProofElements
if err := StemFromBytes(&poly[1], n.stem); err != nil {
return nil, nil, nil, fmt.Errorf("error serializing stem '%x': %w", n.stem, err)
}
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)
}

// First pass: add top-level elements first
var hasC1, hasC2 bool
for _, key := range keys {
hasC1 = hasC1 || (key[31] < 128)
hasC2 = hasC2 || (key[31] >= 128)
if hasC2 {
break
// 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
}
}
}

// 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)
}
} 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")
}

if hasC1 {
pe.Cis = append(pe.Cis, n.commitment)
pe.Zis = append(pe.Zis, 2)
Expand Down

0 comments on commit aae823f

Please sign in to comment.