Skip to content

Commit

Permalink
API: Fix prove bug on API (#3632)
Browse files Browse the repository at this point in the history
## Summary

This PR fixes a bug on algod's API. When a tree contains a missing child (not a full tree), the api handler omits this from the proof response and leads to a root mismatch 

## Test Plan
Add unit tests as well as convert the e2e to test this edge case.
  • Loading branch information
id-ms authored Feb 22, 2022
1 parent eb4af44 commit 88131be
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 17 deletions.
14 changes: 14 additions & 0 deletions crypto/merklearray/proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,17 @@ func (p *SingleLeafProof) GetFixedLengthHashableRepresentation() []byte {
func (p *SingleLeafProof) ToProof() *Proof {
return &p.Proof
}

// GetConcatenatedProof concats the verification path to a single slice
// This function converts an empty element in the path (i.e occurs when the tree is not a full tree)
// into a sequence of digest result of zero.
func (p *SingleLeafProof) GetConcatenatedProof() []byte {
digestSize := p.HashFactory.NewHash().Size()
proofconcat := make([]byte, digestSize*int(p.TreeDepth))
for i := 0; i < int(p.TreeDepth); i++ {
if p.Path[i] != nil {
copy(proofconcat[i*digestSize:(i+1)*digestSize], p.Path[i])
}
}
return proofconcat
}
82 changes: 82 additions & 0 deletions crypto/merklearray/proof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,85 @@ func TestProofSerializationOneLeafTree(t *testing.T) {
}

}

func TestConcatenatedProofsMissingChild(t *testing.T) {
partitiontest.PartitionTest(t)
a := require.New(t)

array := make(TestArray, 7)
for i := 0; i < 7; i++ {
crypto.RandBytes(array[i][:])
}

tree, err := Build(array, crypto.HashFactory{HashType: crypto.Sha512_256})
a.NoError(err)

p, err := tree.ProveSingleLeaf(6)
a.NoError(err)

newP := SingleLeafProof{Proof: Proof{TreeDepth: p.TreeDepth, Path: []crypto.GenericDigest{}, HashFactory: p.HashFactory}}

computedPath := recomputePath(p)

newP.Path = computedPath
err = Verify(tree.Root(), map[uint64]crypto.Hashable{6: array[6]}, newP.ToProof())
a.NoError(err)
}

func TestConcatenatedProofsFullTree(t *testing.T) {
partitiontest.PartitionTest(t)
a := require.New(t)

array := make(TestArray, 8)
for i := 0; i < 8; i++ {
crypto.RandBytes(array[i][:])
}

tree, err := Build(array, crypto.HashFactory{HashType: crypto.Sha512_256})
a.NoError(err)

p, err := tree.ProveSingleLeaf(6)
a.NoError(err)

newP := SingleLeafProof{Proof: Proof{TreeDepth: p.TreeDepth, Path: []crypto.GenericDigest{}, HashFactory: p.HashFactory}}

computedPath := recomputePath(p)

newP.Path = computedPath
err = Verify(tree.Root(), map[uint64]crypto.Hashable{6: array[6]}, newP.ToProof())
a.NoError(err)
}

func TestConcatenatedProofsOneLeaf(t *testing.T) {
partitiontest.PartitionTest(t)
a := require.New(t)

array := make(TestArray, 1)
crypto.RandBytes(array[0][:])

tree, err := Build(array, crypto.HashFactory{HashType: crypto.Sha512_256})
a.NoError(err)

p, err := tree.ProveSingleLeaf(0)
a.NoError(err)

newP := SingleLeafProof{Proof: Proof{TreeDepth: p.TreeDepth, Path: []crypto.GenericDigest{}, HashFactory: p.HashFactory}}

computedPath := recomputePath(p)

newP.Path = computedPath
err = Verify(tree.Root(), map[uint64]crypto.Hashable{0: array[0]}, newP.ToProof())
a.NoError(err)
}

func recomputePath(p *SingleLeafProof) []crypto.GenericDigest {
var computedPath []crypto.GenericDigest
proofconcat := p.GetConcatenatedProof()
for len(proofconcat) > 0 {
var d crypto.Digest
copy(d[:], proofconcat)
computedPath = append(computedPath, d[:])
proofconcat = proofconcat[len(d):]
}
return computedPath
}
9 changes: 2 additions & 7 deletions daemon/algod/api/server/v2/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,20 +388,15 @@ func (v2 *Handlers) GetProof(ctx echo.Context, round uint64, txid string, params
return internalError(ctx, err, "building Merkle tree", v2.Log)
}

proof, err := tree.Prove([]uint64{uint64(idx)})
proof, err := tree.ProveSingleLeaf(uint64(idx))
if err != nil {
return internalError(ctx, err, "generating proof", v2.Log)
}

proofconcat := make([]byte, 0)
for _, proofelem := range proof.Path {
proofconcat = append(proofconcat, proofelem[:]...)
}

stibhash := block.Payset[idx].Hash()

response := generated.ProofResponse{
Proof: proofconcat,
Proof: proof.GetConcatenatedProof(),
Stibhash: stibhash[:],
Idx: uint64(idx),
Treedepth: uint64(proof.TreeDepth),
Expand Down
14 changes: 4 additions & 10 deletions test/e2e-go/features/transactions/proof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func TestTxnMerkleProof(t *testing.T) {
partitiontest.PartitionTest(t)
defer fixtures.ShutdownSynchronizedTest(t)

t.Parallel()
a := require.New(fixtures.SynchronizedTest(t))

var fixture fixtures.RestClientFixture
Expand All @@ -76,7 +77,8 @@ func TestTxnMerkleProof(t *testing.T) {

// Transfer some money to acct0, as well as other random accounts to
// fill up the Merkle tree with more than one element.
for i := 0; i < 10; i++ {
// we do not want to have a full tree in order the catch an empty element edge case
for i := 0; i < 5; i++ {
accti, err := client.GenerateAddress(walletHandle)
a.NoError(err)

Expand All @@ -87,14 +89,6 @@ func TestTxnMerkleProof(t *testing.T) {
tx, err := client.SendPaymentFromUnencryptedWallet(baseAcct, acct0, 1000, 10000000, nil)
a.NoError(err)

for i := 0; i < 10; i++ {
accti, err := client.GenerateAddress(walletHandle)
a.NoError(err)

_, err = client.SendPaymentFromUnencryptedWallet(baseAcct, accti, 1000, 10000000, nil)
a.NoError(err)
}

txid := tx.ID()
confirmedTx, err := fixture.WaitForConfirmedTxn(status.LastRound+10, baseAcct, txid.String())
a.NoError(err)
Expand Down Expand Up @@ -128,7 +122,7 @@ func TestTxnMerkleProof(t *testing.T) {
elems[proofresp.Idx] = &element
err = merklearray.Verify(blk.TxnRoot.ToSlice(), elems, &proof)
if err != nil {
t.Logf("blk.TxnRoot : %v \nproof path %v \ndepth: %d \nStibhash %v", blk.TxnRoot.ToSlice(), proof.Path, proof.TreeDepth, proofresp.Stibhash)
t.Logf("blk.TxnRoot : %v \nproof path %v \ndepth: %d \nStibhash %v\nIndex: %d", blk.TxnRoot.ToSlice(), proof.Path, proof.TreeDepth, proofresp.Stibhash, proofresp.Idx)
a.NoError(err)
}

Expand Down

0 comments on commit 88131be

Please sign in to comment.