From 17163c5596b5cd513f64543f410054fe8fdf0649 Mon Sep 17 00:00:00 2001 From: mattdf Date: Wed, 13 Oct 2021 19:12:18 +0200 Subject: [PATCH 01/19] initial unoptimized subrooth paths traversal function --- subrootpaths.go | 197 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 subrootpaths.go diff --git a/subrootpaths.go b/subrootpaths.go new file mode 100644 index 00000000..4597c481 --- /dev/null +++ b/subrootpaths.go @@ -0,0 +1,197 @@ +package main + +import ( + "errors" + "fmt" + "math" +) + +func subdivide(idxStart uint, width uint) []int { + var path []int + if width == 1 { + return path + } + center := width / 2 + if idxStart < center { + path = append(path, 0) + } else { + idxStart -= center + path = append(path, 1) + } + return append(path, subdivide(idxStart, center)...) +} + +func prune(idxStart uint, pathStart []int, idxEnd uint, pathEnd []int, maxWidth uint) [][]int { + + var prunedPaths [][]int + + // special case of two-share length + if idxStart+1 >= idxEnd { + if idxStart%2 == 1 { + return append(prunedPaths, pathStart, pathEnd) + } else { + return append(prunedPaths, pathStart[:len(pathStart)-1]) + } + } + + // if starting share is on an odd index + if idxStart%2 == 1 { + idxStart += 1 + prunedPaths = append(prunedPaths, pathStart) + pathStart = subdivide(idxStart, maxWidth) + } + + // if ending share is on an even index + if idxEnd%2 == 0 { + idxEnd -= 1 + prunedPaths = append(prunedPaths, pathEnd) + pathEnd = subdivide(idxEnd, maxWidth) + } + + treeDepth := len(pathStart) + capturedSpan := uint(0) + rightTraversed := false + + for i := 1; i < treeDepth; i++ { + nodeSpan := uint(math.Pow(float64(2), float64(i))) + if pathStart[len(pathStart)-i] == 0 { + if (nodeSpan+idxStart)-1 < idxEnd { + // if nodespan is less than end index, continue traversing upwards + capturedSpan = nodeSpan + if rightTraversed { + rightCapture := make([]int, len(pathStart)) + copy(rightCapture, pathStart) + rightCapture[len(pathStart)-i] = 1 + rightCapture = rightCapture[:treeDepth-(i-1)] + prunedPaths = append(prunedPaths, rightCapture) + } else { + rightCapture := make([]int, len(pathStart)-i) + copy(rightCapture, pathStart[:len(pathStart)-i]) + rightCapture[len(pathStart)-i-1] = 1 + prunedPaths = append(prunedPaths, rightCapture) + } + } else if (nodeSpan+idxStart)-1 == idxEnd { + if rightTraversed { + rightCapture := make([]int, len(pathStart)) + copy(rightCapture, pathStart) + rightCapture[len(pathStart)-i] = 1 + rightCapture = rightCapture[:treeDepth-(i-1)] + prunedPaths = append(prunedPaths, rightCapture) + return prunedPaths + } else { + rightCapture := make([]int, len(pathStart)) + copy(rightCapture, pathStart) + //rightCapture[len(pathStart)-i] = 1 + rightCapture = rightCapture[:treeDepth-i] + prunedPaths = append(prunedPaths, rightCapture) + return prunedPaths + } + } else { + // else if it's greater than the end index, break out of the left-capture loop + capturedSpan = nodeSpan/2 - 1 + break + } + } else { + // on a right upwards traverse, we skip processing + // besides adjusting the idxStart for span calculation + // and modifying the previous path calculations to not include + // containing roots as they would span beyond the start index + idxStart = idxStart - nodeSpan/2 + rightTraversed = true + } + } + + var outPath []int + + for i := 1; i < treeDepth; i++ { + // if we ever reach a left branch connection on this loop we've found the final slice + if pathEnd[len(pathEnd)-i] == 0 { + if outPath == nil { + outPath = pathEnd[:len(pathEnd)-(i-1)] + } + break + } else { + nodeSpan := uint(math.Pow(float64(2), float64(i))) + if int(idxEnd)-int(nodeSpan) <= int(capturedSpan) { + rightCapture := make([]int, len(pathEnd)) + copy(rightCapture, pathEnd) + rightCapture[len(pathEnd)-i] = 1 + rightCapture = rightCapture[:treeDepth-(i)] + outPath = rightCapture + } else { + continue + } + } + } + + prunedPaths = append(prunedPaths, outPath) + + return prunedPaths +} + +// Pure function that takes arguments: square size, share index start, +// and share length, and returns a minimal path to the subtree root that +// encompasses that entire range, with the path starting from the +// nearest row root. +func GetSubrootPaths(squareSize uint, idxStart uint, shareLen uint) ([][]int, error) { + + var paths [][]int + shares := squareSize * squareSize + + if shareLen == 0 { + return nil, errors.New("GetSubrootPaths: Can't compute path for 0 length share slice") + } + + // adjust for 0 index + shareLen = shareLen - 1 + + // sanity checking + if idxStart >= shares || idxStart+shareLen >= shares { + return nil, errors.New("GetSubrootPaths: Share slice can't be past the square size") + } + + startRow := int(math.Floor(float64(idxStart) / float64(squareSize))) + endRow := int(math.Ceil(float64(idxStart+shareLen) / float64(squareSize))) + + shareStart := idxStart % squareSize + shareEnd := (idxStart + shareLen) % squareSize + + pathStart := subdivide(shareStart, squareSize) + pathEnd := subdivide(shareEnd, squareSize) + + subtreeLvls := int(math.Log2(float64(squareSize))) - 1 + + if shareLen == 0 { + paths = append(paths, pathStart) + return paths, nil + } + + if startRow == endRow-1 { + paths = append(paths, prune(shareStart, pathStart, shareEnd, pathEnd, squareSize)...) + } else { + prune(shareStart, pathStart, squareSize-1, make([]int, subtreeLvls, subtreeLvls), squareSize) + prune(0, make([]int, subtreeLvls, subtreeLvls), shareEnd, pathEnd, squareSize) + } + + return paths, nil +} + +func main() { + // fmt.Println(getSubrootPaths(8, 0, 7)) + // fmt.Println(getSubrootPaths(8, 3, 1)) + // fmt.Println(getSubrootPaths(8, 2, 3)) + // fmt.Println(getSubrootPaths(8, 2, 4)) + // fmt.Println(getSubrootPaths(8, 0, 2)) + fmt.Println("32, 0, 4") + fmt.Println(GetSubrootPaths(32, 0, 4)) + fmt.Println("32, 1, 8") + fmt.Println(GetSubrootPaths(32, 1, 8)) + fmt.Println("32, 1, 11") + fmt.Println(GetSubrootPaths(32, 1, 11)) + fmt.Println("32, 18, 11") + fmt.Println(GetSubrootPaths(32, 18, 11)) + fmt.Println("4, 0, 1") + fmt.Println(GetSubrootPaths(4, 0, 1)) + // fmt.Println(getSubrootPaths(32, 1, 16)) + // fmt.Println(getSubrootPaths(32, 0, 16)) +} From ad46107610c9cc28d3bb8f5d1ef2e96424298f5d Mon Sep 17 00:00:00 2001 From: mattdf Date: Thu, 14 Oct 2021 16:49:20 +0200 Subject: [PATCH 02/19] cleanup and optimization --- subrootpaths.go | 90 ++++++++++++++++---------------------------- subrootpaths_test.go | 28 ++++++++++++++ 2 files changed, 61 insertions(+), 57 deletions(-) create mode 100644 subrootpaths_test.go diff --git a/subrootpaths.go b/subrootpaths.go index 4597c481..393edbe1 100644 --- a/subrootpaths.go +++ b/subrootpaths.go @@ -1,8 +1,7 @@ -package main +package nmt import ( "errors" - "fmt" "math" ) @@ -21,9 +20,18 @@ func subdivide(idxStart uint, width uint) []int { return append(path, subdivide(idxStart, center)...) } +func extractBranch(path []int, depth int, index int, offset int) []int { + rightCapture := make([]int, len(path)) + copy(rightCapture, path) + rightCapture[len(path)-index] = 1 + rightCapture = rightCapture[:depth-(index-offset)] + return rightCapture +} + func prune(idxStart uint, pathStart []int, idxEnd uint, pathEnd []int, maxWidth uint) [][]int { var prunedPaths [][]int + var preprocessedPaths [][]int // special case of two-share length if idxStart+1 >= idxEnd { @@ -37,14 +45,14 @@ func prune(idxStart uint, pathStart []int, idxEnd uint, pathEnd []int, maxWidth // if starting share is on an odd index if idxStart%2 == 1 { idxStart += 1 - prunedPaths = append(prunedPaths, pathStart) + preprocessedPaths = append(preprocessedPaths, pathStart) pathStart = subdivide(idxStart, maxWidth) } // if ending share is on an even index if idxEnd%2 == 0 { idxEnd -= 1 - prunedPaths = append(prunedPaths, pathEnd) + preprocessedPaths = append(preprocessedPaths, pathEnd) pathEnd = subdivide(idxEnd, maxWidth) } @@ -58,33 +66,22 @@ func prune(idxStart uint, pathStart []int, idxEnd uint, pathEnd []int, maxWidth if (nodeSpan+idxStart)-1 < idxEnd { // if nodespan is less than end index, continue traversing upwards capturedSpan = nodeSpan + // if a right path has been encountered, we want to return the right + // branch one level down if rightTraversed { - rightCapture := make([]int, len(pathStart)) - copy(rightCapture, pathStart) - rightCapture[len(pathStart)-i] = 1 - rightCapture = rightCapture[:treeDepth-(i-1)] - prunedPaths = append(prunedPaths, rightCapture) + prunedPaths = append(prunedPaths, extractBranch(pathStart, treeDepth, i, 1)) } else { - rightCapture := make([]int, len(pathStart)-i) - copy(rightCapture, pathStart[:len(pathStart)-i]) - rightCapture[len(pathStart)-i-1] = 1 - prunedPaths = append(prunedPaths, rightCapture) + prunedPaths = append(prunedPaths, extractBranch(pathStart, treeDepth, i, 0)) } } else if (nodeSpan+idxStart)-1 == idxEnd { + // if it's equal to the end index, this is the final root to return if rightTraversed { - rightCapture := make([]int, len(pathStart)) - copy(rightCapture, pathStart) - rightCapture[len(pathStart)-i] = 1 - rightCapture = rightCapture[:treeDepth-(i-1)] - prunedPaths = append(prunedPaths, rightCapture) - return prunedPaths + prunedPaths = append(prunedPaths, extractBranch(pathStart, treeDepth, i, 1)) + return append(preprocessedPaths, prunedPaths...) } else { - rightCapture := make([]int, len(pathStart)) - copy(rightCapture, pathStart) - //rightCapture[len(pathStart)-i] = 1 - rightCapture = rightCapture[:treeDepth-i] - prunedPaths = append(prunedPaths, rightCapture) - return prunedPaths + // if we've never traversed right then this is a special case + // where the last root found here encompasses the whole lower tree + return append(make([][]int, 0), pathStart[:treeDepth-i]) } } else { // else if it's greater than the end index, break out of the left-capture loop @@ -113,20 +110,15 @@ func prune(idxStart uint, pathStart []int, idxEnd uint, pathEnd []int, maxWidth } else { nodeSpan := uint(math.Pow(float64(2), float64(i))) if int(idxEnd)-int(nodeSpan) <= int(capturedSpan) { - rightCapture := make([]int, len(pathEnd)) - copy(rightCapture, pathEnd) - rightCapture[len(pathEnd)-i] = 1 - rightCapture = rightCapture[:treeDepth-(i)] - outPath = rightCapture - } else { - continue + // traverse upwards while updating the latest path found + outPath = extractBranch(pathEnd, treeDepth, i, 0) } } } prunedPaths = append(prunedPaths, outPath) - return prunedPaths + return append(preprocessedPaths, prunedPaths...) } // Pure function that takes arguments: square size, share index start, @@ -159,8 +151,6 @@ func GetSubrootPaths(squareSize uint, idxStart uint, shareLen uint) ([][]int, er pathStart := subdivide(shareStart, squareSize) pathEnd := subdivide(shareEnd, squareSize) - subtreeLvls := int(math.Log2(float64(squareSize))) - 1 - if shareLen == 0 { paths = append(paths, pathStart) return paths, nil @@ -169,29 +159,15 @@ func GetSubrootPaths(squareSize uint, idxStart uint, shareLen uint) ([][]int, er if startRow == endRow-1 { paths = append(paths, prune(shareStart, pathStart, shareEnd, pathEnd, squareSize)...) } else { - prune(shareStart, pathStart, squareSize-1, make([]int, subtreeLvls, subtreeLvls), squareSize) - prune(0, make([]int, subtreeLvls, subtreeLvls), shareEnd, pathEnd, squareSize) + rightEndPath := subdivide(squareSize-1, squareSize) + leftEndPath := subdivide(0, squareSize) + for i := 0; i < (endRow-startRow)-1; i++ { + var p []int + paths = append(paths, p) + } + paths = append(paths, prune(shareStart, pathStart, squareSize-1, rightEndPath, squareSize)...) + paths = append(paths, prune(0, leftEndPath, shareEnd, pathEnd, squareSize)...) } return paths, nil } - -func main() { - // fmt.Println(getSubrootPaths(8, 0, 7)) - // fmt.Println(getSubrootPaths(8, 3, 1)) - // fmt.Println(getSubrootPaths(8, 2, 3)) - // fmt.Println(getSubrootPaths(8, 2, 4)) - // fmt.Println(getSubrootPaths(8, 0, 2)) - fmt.Println("32, 0, 4") - fmt.Println(GetSubrootPaths(32, 0, 4)) - fmt.Println("32, 1, 8") - fmt.Println(GetSubrootPaths(32, 1, 8)) - fmt.Println("32, 1, 11") - fmt.Println(GetSubrootPaths(32, 1, 11)) - fmt.Println("32, 18, 11") - fmt.Println(GetSubrootPaths(32, 18, 11)) - fmt.Println("4, 0, 1") - fmt.Println(GetSubrootPaths(4, 0, 1)) - // fmt.Println(getSubrootPaths(32, 1, 16)) - // fmt.Println(getSubrootPaths(32, 0, 16)) -} diff --git a/subrootpaths_test.go b/subrootpaths_test.go new file mode 100644 index 00000000..14aeda7c --- /dev/null +++ b/subrootpaths_test.go @@ -0,0 +1,28 @@ +package nmt + +import ( + "fmt" + "testing" +) + +func TestPathGeneration(t *testing.T) { + // fmt.Println(getSubrootPaths(8, 0, 7)) + // fmt.Println(getSubrootPaths(8, 3, 1)) + // fmt.Println(getSubrootPaths(8, 2, 3)) + // fmt.Println(getSubrootPaths(8, 2, 4)) + // fmt.Println(getSubrootPaths(8, 0, 2)) + fmt.Println("32, 0, 4") + fmt.Println(GetSubrootPaths(32, 0, 4)) + fmt.Println("32, 1, 8") + fmt.Println(GetSubrootPaths(32, 1, 8)) + fmt.Println("32, 1, 11") + fmt.Println(GetSubrootPaths(32, 1, 11)) + fmt.Println("32, 18, 11") + fmt.Println(GetSubrootPaths(32, 18, 11)) + fmt.Println("4, 0, 1") + fmt.Println(GetSubrootPaths(4, 0, 1)) + fmt.Println("16, 0, 8") + fmt.Println(GetSubrootPaths(16, 0, 8)) + // fmt.Println(getSubrootPaths(32, 1, 16)) + // fmt.Println(getSubrootPaths(32, 0, 16)) +} From f54fbf6b758ad35f9bf2e00a06582345025b84e6 Mon Sep 17 00:00:00 2001 From: mattdf Date: Thu, 14 Oct 2021 17:01:09 +0200 Subject: [PATCH 03/19] fix mistakenly omitted preprocessed roots --- subrootpaths.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subrootpaths.go b/subrootpaths.go index 393edbe1..8583eafe 100644 --- a/subrootpaths.go +++ b/subrootpaths.go @@ -81,7 +81,7 @@ func prune(idxStart uint, pathStart []int, idxEnd uint, pathEnd []int, maxWidth } else { // if we've never traversed right then this is a special case // where the last root found here encompasses the whole lower tree - return append(make([][]int, 0), pathStart[:treeDepth-i]) + return append(preprocessedPaths, pathStart[:treeDepth-i]) } } else { // else if it's greater than the end index, break out of the left-capture loop From 16b0afccc6f8ae6969df28f636e3d6f33817e561 Mon Sep 17 00:00:00 2001 From: mattdf Date: Thu, 14 Oct 2021 17:35:19 +0200 Subject: [PATCH 04/19] make comments more helpful, and special case check clearer --- subrootpaths.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/subrootpaths.go b/subrootpaths.go index 8583eafe..a12041d0 100644 --- a/subrootpaths.go +++ b/subrootpaths.go @@ -33,7 +33,7 @@ func prune(idxStart uint, pathStart []int, idxEnd uint, pathEnd []int, maxWidth var prunedPaths [][]int var preprocessedPaths [][]int - // special case of two-share length + // special case of two-share length, just return one or two paths if idxStart+1 >= idxEnd { if idxStart%2 == 1 { return append(prunedPaths, pathStart, pathEnd) @@ -42,14 +42,14 @@ func prune(idxStart uint, pathStart []int, idxEnd uint, pathEnd []int, maxWidth } } - // if starting share is on an odd index + // if starting share is on an odd index, add that single path and shift it right 1 if idxStart%2 == 1 { idxStart += 1 preprocessedPaths = append(preprocessedPaths, pathStart) pathStart = subdivide(idxStart, maxWidth) } - // if ending share is on an even index + // if ending share is on an even index, add that single index and shift it left 1 if idxEnd%2 == 0 { idxEnd -= 1 preprocessedPaths = append(preprocessedPaths, pathEnd) @@ -63,14 +63,15 @@ func prune(idxStart uint, pathStart []int, idxEnd uint, pathEnd []int, maxWidth for i := 1; i < treeDepth; i++ { nodeSpan := uint(math.Pow(float64(2), float64(i))) if pathStart[len(pathStart)-i] == 0 { + // if nodespan is less than end index, continue traversing upwards if (nodeSpan+idxStart)-1 < idxEnd { - // if nodespan is less than end index, continue traversing upwards capturedSpan = nodeSpan // if a right path has been encountered, we want to return the right // branch one level down if rightTraversed { prunedPaths = append(prunedPaths, extractBranch(pathStart, treeDepth, i, 1)) } else { + // else add the current root node prunedPaths = append(prunedPaths, extractBranch(pathStart, treeDepth, i, 0)) } } else if (nodeSpan+idxStart)-1 == idxEnd { @@ -130,6 +131,7 @@ func GetSubrootPaths(squareSize uint, idxStart uint, shareLen uint) ([][]int, er var paths [][]int shares := squareSize * squareSize + // no path exists for 0 length slice if shareLen == 0 { return nil, errors.New("GetSubrootPaths: Can't compute path for 0 length share slice") } @@ -151,21 +153,26 @@ func GetSubrootPaths(squareSize uint, idxStart uint, shareLen uint) ([][]int, er pathStart := subdivide(shareStart, squareSize) pathEnd := subdivide(shareEnd, squareSize) - if shareLen == 0 { + // if the length is one, just return the subdivided start path + if shareStart == shareEnd { paths = append(paths, pathStart) return paths, nil } + // if the shares are all in one row, do the normal case if startRow == endRow-1 { paths = append(paths, prune(shareStart, pathStart, shareEnd, pathEnd, squareSize)...) } else { + // if the shares span multiple rows, treat it as 2 different path generations, + // one from left-most root to end of a row, and one from start of a row to right-most root, + // and returning nil lists for the fully covered rows in between rightEndPath := subdivide(squareSize-1, squareSize) leftEndPath := subdivide(0, squareSize) + paths = append(paths, prune(shareStart, pathStart, squareSize-1, rightEndPath, squareSize)...) for i := 0; i < (endRow-startRow)-1; i++ { var p []int paths = append(paths, p) } - paths = append(paths, prune(shareStart, pathStart, squareSize-1, rightEndPath, squareSize)...) paths = append(paths, prune(0, leftEndPath, shareEnd, pathEnd, squareSize)...) } From 69853e8bf60c418c53805ecb911b87d4b7bfad96 Mon Sep 17 00:00:00 2001 From: mattdf Date: Thu, 14 Oct 2021 17:38:41 +0200 Subject: [PATCH 05/19] undo error for special case check --- subrootpaths.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subrootpaths.go b/subrootpaths.go index a12041d0..5f4baea5 100644 --- a/subrootpaths.go +++ b/subrootpaths.go @@ -154,7 +154,7 @@ func GetSubrootPaths(squareSize uint, idxStart uint, shareLen uint) ([][]int, er pathEnd := subdivide(shareEnd, squareSize) // if the length is one, just return the subdivided start path - if shareStart == shareEnd { + if shareLen == 0 { paths = append(paths, pathStart) return paths, nil } From aad7957b5ca343296de86f0e972187b3de467865 Mon Sep 17 00:00:00 2001 From: mattdf Date: Fri, 15 Oct 2021 10:29:33 +0200 Subject: [PATCH 06/19] add more comprehensive tests, fix some issues, make GetSubrootPaths return a 3d list --- subrootpaths.go | 49 +++++++++-------- subrootpaths_test.go | 126 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 134 insertions(+), 41 deletions(-) diff --git a/subrootpaths.go b/subrootpaths.go index 5f4baea5..6f46228c 100644 --- a/subrootpaths.go +++ b/subrootpaths.go @@ -24,8 +24,7 @@ func extractBranch(path []int, depth int, index int, offset int) []int { rightCapture := make([]int, len(path)) copy(rightCapture, path) rightCapture[len(path)-index] = 1 - rightCapture = rightCapture[:depth-(index-offset)] - return rightCapture + return rightCapture[:depth-(index-offset)] } func prune(idxStart uint, pathStart []int, idxEnd uint, pathEnd []int, maxWidth uint) [][]int { @@ -60,7 +59,7 @@ func prune(idxStart uint, pathStart []int, idxEnd uint, pathEnd []int, maxWidth capturedSpan := uint(0) rightTraversed := false - for i := 1; i < treeDepth; i++ { + for i := 1; i <= treeDepth; i++ { nodeSpan := uint(math.Pow(float64(2), float64(i))) if pathStart[len(pathStart)-i] == 0 { // if nodespan is less than end index, continue traversing upwards @@ -101,7 +100,7 @@ func prune(idxStart uint, pathStart []int, idxEnd uint, pathEnd []int, maxWidth var outPath []int - for i := 1; i < treeDepth; i++ { + for i := 1; i <= treeDepth; i++ { // if we ever reach a left branch connection on this loop we've found the final slice if pathEnd[len(pathEnd)-i] == 0 { if outPath == nil { @@ -122,15 +121,25 @@ func prune(idxStart uint, pathStart []int, idxEnd uint, pathEnd []int, maxWidth return append(preprocessedPaths, prunedPaths...) } -// Pure function that takes arguments: square size, share index start, -// and share length, and returns a minimal path to the subtree root that -// encompasses that entire range, with the path starting from the -// nearest row root. -func GetSubrootPaths(squareSize uint, idxStart uint, shareLen uint) ([][]int, error) { +// GetSubrootPaths is a pure function that takes arguments: square size, share index start, +// and share length, and returns a minimal set of paths to the subtree roots that +// encompasses that entire range of shares, with each top level entry in the list +// starting from the nearest row root. +// +// An empty entry in the top level list means the shares span that entire row and so +// the root for that segment of shares is equivalent to the row root. +func GetSubrootPaths(squareSize uint, idxStart uint, shareLen uint) ([][][]int, error) { var paths [][]int + var top [][][]int + shares := squareSize * squareSize + // check if squareSize is a power of 2 by checking that only 1 bit is on + if squareSize < 2 || !((squareSize & (squareSize - 1)) == 0) { + return nil, errors.New("GetSubrootPaths: Supplied square size is not a power of 2") + } + // no path exists for 0 length slice if shareLen == 0 { return nil, errors.New("GetSubrootPaths: Can't compute path for 0 length share slice") @@ -155,26 +164,24 @@ func GetSubrootPaths(squareSize uint, idxStart uint, shareLen uint) ([][]int, er // if the length is one, just return the subdivided start path if shareLen == 0 { - paths = append(paths, pathStart) - return paths, nil + return append(top, append(paths, pathStart)), nil } // if the shares are all in one row, do the normal case if startRow == endRow-1 { - paths = append(paths, prune(shareStart, pathStart, shareEnd, pathEnd, squareSize)...) + top = append(top, prune(shareStart, pathStart, shareEnd, pathEnd, squareSize)) } else { // if the shares span multiple rows, treat it as 2 different path generations, // one from left-most root to end of a row, and one from start of a row to right-most root, - // and returning nil lists for the fully covered rows in between - rightEndPath := subdivide(squareSize-1, squareSize) - leftEndPath := subdivide(0, squareSize) - paths = append(paths, prune(shareStart, pathStart, squareSize-1, rightEndPath, squareSize)...) - for i := 0; i < (endRow-startRow)-1; i++ { - var p []int - paths = append(paths, p) + // and returning nil lists for the fully covered rows in between= + left, _ := GetSubrootPaths(squareSize, idxStart, squareSize-idxStart) + right, _ := GetSubrootPaths(squareSize, 0, shareEnd+1) + top = append(top, left[0]) + for i := 1; i < (endRow-startRow)-1; i++ { + top = append(top, [][]int{{}}) } - paths = append(paths, prune(0, leftEndPath, shareEnd, pathEnd, squareSize)...) + top = append(top, right[0]) } - return paths, nil + return top, nil } diff --git a/subrootpaths_test.go b/subrootpaths_test.go index 14aeda7c..eca78737 100644 --- a/subrootpaths_test.go +++ b/subrootpaths_test.go @@ -1,28 +1,114 @@ package nmt import ( - "fmt" + "reflect" "testing" ) +func TestArgValidation(t *testing.T) { + var err error + var paths [][][]int + + paths, err = GetSubrootPaths(0, 0, 0) + if err == nil { + t.Fatalf(`GetSubrootPaths(0, 0, 0) = %v, %v, want square size error`, paths, err) + } + + paths, err = GetSubrootPaths(1, 0, 1) + if err == nil { + t.Fatalf(`GetSubrootPaths(1, 0, 1) = %v, %v, want square size error`, paths, err) + } + + paths, err = GetSubrootPaths(20, 0, 1) + if err == nil { + t.Fatalf(`GetSubrootPaths(20, 0, 1) = %v, %v, want square size error`, paths, err) + } + + paths, err = GetSubrootPaths(4, 0, 17) + if err == nil { + t.Fatalf(`GetSubrootPaths(4, 0, 17) = %v, %v, want length past square size error`, paths, err) + } + + paths, err = GetSubrootPaths(4, 0, 0) + if err == nil { + t.Fatalf(`GetSubrootPaths(4, 0, 0) = %v, %v, want invalid share size error`, paths, err) + } +} + func TestPathGeneration(t *testing.T) { - // fmt.Println(getSubrootPaths(8, 0, 7)) - // fmt.Println(getSubrootPaths(8, 3, 1)) - // fmt.Println(getSubrootPaths(8, 2, 3)) - // fmt.Println(getSubrootPaths(8, 2, 4)) - // fmt.Println(getSubrootPaths(8, 0, 2)) - fmt.Println("32, 0, 4") - fmt.Println(GetSubrootPaths(32, 0, 4)) - fmt.Println("32, 1, 8") - fmt.Println(GetSubrootPaths(32, 1, 8)) - fmt.Println("32, 1, 11") - fmt.Println(GetSubrootPaths(32, 1, 11)) - fmt.Println("32, 18, 11") - fmt.Println(GetSubrootPaths(32, 18, 11)) - fmt.Println("4, 0, 1") - fmt.Println(GetSubrootPaths(4, 0, 1)) - fmt.Println("16, 0, 8") - fmt.Println(GetSubrootPaths(16, 0, 8)) - // fmt.Println(getSubrootPaths(32, 1, 16)) - // fmt.Println(getSubrootPaths(32, 0, 16)) + + var err error + var paths [][][]int + + paths, err = GetSubrootPaths(2, 0, 2) + { + check := [][][]int{{{}}} + if !reflect.DeepEqual(paths, check) { + t.Fatalf(`GetSubrootPaths(2, 0, 2) = %v, %v, want %v`, paths, err, check) + } + } + + paths, err = GetSubrootPaths(2, 0, 1) + { + check := [][][]int{{{0}}} + if !reflect.DeepEqual(paths, check) { + t.Fatalf(`GetSubrootPaths(2, 0, 1) = %v, %v, want %v`, paths, err, check) + } + } + + paths, err = GetSubrootPaths(2, 1, 1) + { + check := [][][]int{{{1}}} + if !reflect.DeepEqual(paths, check) { + t.Fatalf(`GetSubrootPaths(2, 1, 1) = %v, %v, want %v`, paths, err, check) + } + } + + paths, err = GetSubrootPaths(8, 1, 6) + { + check := [][][]int{{{0, 0, 1}, {1, 1, 0}, {0, 1}, {1, 0}}} + if !reflect.DeepEqual(paths, check) { + t.Fatalf(`GetSubrootPaths(8, 1, 6) = %v, %v, want %v`, paths, err, check) + } + } + + paths, err = GetSubrootPaths(32, 0, 32) + { + check := [][][]int{{{}}} + if !reflect.DeepEqual(paths, check) { + t.Fatalf(`GetSubrootPaths(32, 0, 32) = %v, %v, want %v`, paths, err, check) + } + } + + paths, err = GetSubrootPaths(32, 0, 64) + { + check := [][][]int{{{}}, {{}}} + if !reflect.DeepEqual(paths, check) { + t.Fatalf(`GetSubrootPaths(32, 0, 64) = %v, %v, want %v`, paths, err, check) + } + } + + paths, err = GetSubrootPaths(32, 0, 96) + { + check := [][][]int{{{}}, {{}}, {{}}} + if !reflect.DeepEqual(paths, check) { + t.Fatalf(`GetSubrootPaths(32, 0, 96) = %v, %v, want %v`, paths, err, check) + } + } + + paths, err = GetSubrootPaths(32, 18, 11) + { + check := [][][]int{{{1, 1, 1, 0, 0}, {1, 0, 0, 1}, {1, 0, 1}, {1, 1, 0}}} + if !reflect.DeepEqual(paths, check) { + t.Fatalf(`GetSubrootPaths(32, 18, 11) = %v, %v, want %v`, paths, err, check) + } + } + + paths, err = GetSubrootPaths(32, 48, 16) + { + check := [][][]int{{{1}}} + if !reflect.DeepEqual(paths, check) { + t.Fatalf(`GetSubrootPaths(32, 18, 11) = %v, %v, want %v`, paths, err, check) + } + } } From 814ffd81a3254c7ddaba7e60bbe14ca6bbaa5545 Mon Sep 17 00:00:00 2001 From: mattdf Date: Fri, 15 Oct 2021 10:47:43 +0200 Subject: [PATCH 07/19] add test case for 100% coverage --- subrootpaths_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/subrootpaths_test.go b/subrootpaths_test.go index eca78737..20564153 100644 --- a/subrootpaths_test.go +++ b/subrootpaths_test.go @@ -64,6 +64,14 @@ func TestPathGeneration(t *testing.T) { } } + paths, err = GetSubrootPaths(4, 1, 2) + { + check := [][][]int{{{0, 1}, {1, 0}}} + if !reflect.DeepEqual(paths, check) { + t.Fatalf(`GetSubrootPaths(4, 1, 2) = %v, %v, want %v`, paths, err, check) + } + } + paths, err = GetSubrootPaths(8, 1, 6) { check := [][][]int{{{0, 0, 1}, {1, 1, 0}, {0, 1}, {1, 0}}} @@ -104,6 +112,14 @@ func TestPathGeneration(t *testing.T) { } } + paths, err = GetSubrootPaths(32, 14, 18) + { + check := [][][]int{{{0, 1, 1, 1}, {1}}} + if !reflect.DeepEqual(paths, check) { + t.Fatalf(`GetSubrootPaths(32, 14, 18) = %v, %v, want %v`, paths, err, check) + } + } + paths, err = GetSubrootPaths(32, 48, 16) { check := [][][]int{{{1}}} From cfbc330829be5ee4928855f156d723beb7d29adf Mon Sep 17 00:00:00 2001 From: mattdf Date: Fri, 15 Oct 2021 11:33:22 +0200 Subject: [PATCH 08/19] change to table tests, assign named errors --- subrootpaths.go | 10 +++- subrootpaths_test.go | 137 +++++++++++-------------------------------- 2 files changed, 40 insertions(+), 107 deletions(-) diff --git a/subrootpaths.go b/subrootpaths.go index 6f46228c..40b6cf4a 100644 --- a/subrootpaths.go +++ b/subrootpaths.go @@ -5,6 +5,10 @@ import ( "math" ) +var SRPNotPowerOf2 = errors.New("GetSubrootPaths: Supplied square size is not a power of 2") +var SRPInvalidShareSize = errors.New("GetSubrootPaths: Can't compute path for 0 length share slice") +var SRPPastSquareSize = errors.New("GetSubrootPaths: Share slice can't be past the square size") + func subdivide(idxStart uint, width uint) []int { var path []int if width == 1 { @@ -137,12 +141,12 @@ func GetSubrootPaths(squareSize uint, idxStart uint, shareLen uint) ([][][]int, // check if squareSize is a power of 2 by checking that only 1 bit is on if squareSize < 2 || !((squareSize & (squareSize - 1)) == 0) { - return nil, errors.New("GetSubrootPaths: Supplied square size is not a power of 2") + return nil, SRPNotPowerOf2 } // no path exists for 0 length slice if shareLen == 0 { - return nil, errors.New("GetSubrootPaths: Can't compute path for 0 length share slice") + return nil, SRPInvalidShareSize } // adjust for 0 index @@ -150,7 +154,7 @@ func GetSubrootPaths(squareSize uint, idxStart uint, shareLen uint) ([][][]int, // sanity checking if idxStart >= shares || idxStart+shareLen >= shares { - return nil, errors.New("GetSubrootPaths: Share slice can't be past the square size") + return nil, SRPPastSquareSize } startRow := int(math.Floor(float64(idxStart) / float64(squareSize))) diff --git a/subrootpaths_test.go b/subrootpaths_test.go index 20564153..14094f26 100644 --- a/subrootpaths_test.go +++ b/subrootpaths_test.go @@ -6,125 +6,54 @@ import ( ) func TestArgValidation(t *testing.T) { - var err error - var paths [][][]int - paths, err = GetSubrootPaths(0, 0, 0) - if err == nil { - t.Fatalf(`GetSubrootPaths(0, 0, 0) = %v, %v, want square size error`, paths, err) + type test struct { + input [3]uint + want error } - paths, err = GetSubrootPaths(1, 0, 1) - if err == nil { - t.Fatalf(`GetSubrootPaths(1, 0, 1) = %v, %v, want square size error`, paths, err) + tests := []test{ + {input: [3]uint{0, 0, 0}, want: SRPNotPowerOf2}, + {input: [3]uint{1, 0, 1}, want: SRPNotPowerOf2}, + {input: [3]uint{20, 0, 1}, want: SRPNotPowerOf2}, + {input: [3]uint{4, 0, 17}, want: SRPPastSquareSize}, + {input: [3]uint{4, 0, 0}, want: SRPInvalidShareSize}, } - paths, err = GetSubrootPaths(20, 0, 1) - if err == nil { - t.Fatalf(`GetSubrootPaths(20, 0, 1) = %v, %v, want square size error`, paths, err) - } - - paths, err = GetSubrootPaths(4, 0, 17) - if err == nil { - t.Fatalf(`GetSubrootPaths(4, 0, 17) = %v, %v, want length past square size error`, paths, err) - } - - paths, err = GetSubrootPaths(4, 0, 0) - if err == nil { - t.Fatalf(`GetSubrootPaths(4, 0, 0) = %v, %v, want invalid share size error`, paths, err) + for _, tc := range tests { + paths, err := GetSubrootPaths(tc.input[0], tc.input[1], tc.input[2]) + if err != tc.want { + t.Fatalf(`GetSubrootPaths(%v) = %v, %v, want %v`, tc.input, paths, err, tc.want) + } } } func TestPathGeneration(t *testing.T) { - var err error - var paths [][][]int - - paths, err = GetSubrootPaths(2, 0, 2) - { - check := [][][]int{{{}}} - if !reflect.DeepEqual(paths, check) { - t.Fatalf(`GetSubrootPaths(2, 0, 2) = %v, %v, want %v`, paths, err, check) - } - } - - paths, err = GetSubrootPaths(2, 0, 1) - { - check := [][][]int{{{0}}} - if !reflect.DeepEqual(paths, check) { - t.Fatalf(`GetSubrootPaths(2, 0, 1) = %v, %v, want %v`, paths, err, check) - } - } - - paths, err = GetSubrootPaths(2, 1, 1) - { - check := [][][]int{{{1}}} - if !reflect.DeepEqual(paths, check) { - t.Fatalf(`GetSubrootPaths(2, 1, 1) = %v, %v, want %v`, paths, err, check) - } + type test struct { + input [3]uint + want [][][]int } - paths, err = GetSubrootPaths(4, 1, 2) - { - check := [][][]int{{{0, 1}, {1, 0}}} - if !reflect.DeepEqual(paths, check) { - t.Fatalf(`GetSubrootPaths(4, 1, 2) = %v, %v, want %v`, paths, err, check) - } + tests := []test{ + {input: [3]uint{2, 0, 2}, want: [][][]int{{{}}}}, + {input: [3]uint{2, 0, 1}, want: [][][]int{{{0}}}}, + {input: [3]uint{2, 1, 1}, want: [][][]int{{{1}}}}, + {input: [3]uint{4, 1, 2}, want: [][][]int{{{0, 1}, {1, 0}}}}, + {input: [3]uint{8, 1, 6}, want: [][][]int{{{0, 0, 1}, {1, 1, 0}, {0, 1}, {1, 0}}}}, + {input: [3]uint{32, 0, 32}, want: [][][]int{{{}}}}, + {input: [3]uint{32, 0, 64}, want: [][][]int{{{}}, {{}}}}, + {input: [3]uint{32, 0, 96}, want: [][][]int{{{}}, {{}}, {{}}}}, + {input: [3]uint{32, 18, 11}, want: [][][]int{{{1, 1, 1, 0, 0}, {1, 0, 0, 1}, {1, 0, 1}, {1, 1, 0}}}}, + {input: [3]uint{32, 14, 18}, want: [][][]int{{{0, 1, 1, 1}, {1}}}}, + {input: [3]uint{32, 48, 16}, want: [][][]int{{{1}}}}, } - paths, err = GetSubrootPaths(8, 1, 6) - { - check := [][][]int{{{0, 0, 1}, {1, 1, 0}, {0, 1}, {1, 0}}} - if !reflect.DeepEqual(paths, check) { - t.Fatalf(`GetSubrootPaths(8, 1, 6) = %v, %v, want %v`, paths, err, check) + for _, tc := range tests { + paths, err := GetSubrootPaths(tc.input[0], tc.input[1], tc.input[2]) + if !reflect.DeepEqual(paths, tc.want) { + t.Fatalf(`GetSubrootPaths(%v) = %v, %v, want %v`, tc.input, paths, err, tc.want) } } - paths, err = GetSubrootPaths(32, 0, 32) - { - check := [][][]int{{{}}} - if !reflect.DeepEqual(paths, check) { - t.Fatalf(`GetSubrootPaths(32, 0, 32) = %v, %v, want %v`, paths, err, check) - } - } - - paths, err = GetSubrootPaths(32, 0, 64) - { - check := [][][]int{{{}}, {{}}} - if !reflect.DeepEqual(paths, check) { - t.Fatalf(`GetSubrootPaths(32, 0, 64) = %v, %v, want %v`, paths, err, check) - } - } - - paths, err = GetSubrootPaths(32, 0, 96) - { - check := [][][]int{{{}}, {{}}, {{}}} - if !reflect.DeepEqual(paths, check) { - t.Fatalf(`GetSubrootPaths(32, 0, 96) = %v, %v, want %v`, paths, err, check) - } - } - - paths, err = GetSubrootPaths(32, 18, 11) - { - check := [][][]int{{{1, 1, 1, 0, 0}, {1, 0, 0, 1}, {1, 0, 1}, {1, 1, 0}}} - if !reflect.DeepEqual(paths, check) { - t.Fatalf(`GetSubrootPaths(32, 18, 11) = %v, %v, want %v`, paths, err, check) - } - } - - paths, err = GetSubrootPaths(32, 14, 18) - { - check := [][][]int{{{0, 1, 1, 1}, {1}}} - if !reflect.DeepEqual(paths, check) { - t.Fatalf(`GetSubrootPaths(32, 14, 18) = %v, %v, want %v`, paths, err, check) - } - } - - paths, err = GetSubrootPaths(32, 48, 16) - { - check := [][][]int{{{1}}} - if !reflect.DeepEqual(paths, check) { - t.Fatalf(`GetSubrootPaths(32, 18, 11) = %v, %v, want %v`, paths, err, check) - } - } } From 0d43a25f1f194703617a35ef6b9d737ea6ded840 Mon Sep 17 00:00:00 2001 From: mattdf Date: Fri, 15 Oct 2021 12:13:26 +0200 Subject: [PATCH 09/19] replace recursive subdivide with bitwise extraction --- subrootpaths.go | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/subrootpaths.go b/subrootpaths.go index 40b6cf4a..c6148320 100644 --- a/subrootpaths.go +++ b/subrootpaths.go @@ -11,17 +11,15 @@ var SRPPastSquareSize = errors.New("GetSubrootPaths: Share slice can't be past t func subdivide(idxStart uint, width uint) []int { var path []int - if width == 1 { - return path - } - center := width / 2 - if idxStart < center { - path = append(path, 0) - } else { - idxStart -= center - path = append(path, 1) + pathlen := int(math.Log2(float64(width))) + for i := pathlen - 1; i >= 0; i-- { + if (idxStart & (1 << i)) == 0 { + path = append(path, 0) + } else { + path = append(path, 1) + } } - return append(path, subdivide(idxStart, center)...) + return path } func extractBranch(path []int, depth int, index int, offset int) []int { @@ -47,14 +45,14 @@ func prune(idxStart uint, pathStart []int, idxEnd uint, pathEnd []int, maxWidth // if starting share is on an odd index, add that single path and shift it right 1 if idxStart%2 == 1 { - idxStart += 1 + idxStart++ preprocessedPaths = append(preprocessedPaths, pathStart) pathStart = subdivide(idxStart, maxWidth) } // if ending share is on an even index, add that single index and shift it left 1 if idxEnd%2 == 0 { - idxEnd -= 1 + idxEnd-- preprocessedPaths = append(preprocessedPaths, pathEnd) pathEnd = subdivide(idxEnd, maxWidth) } From 1654b4fadc111db9da126821abf865546ffcde4e Mon Sep 17 00:00:00 2001 From: mattdf Date: Fri, 15 Oct 2021 16:54:13 +0200 Subject: [PATCH 10/19] generalize prune function and fix edge case, add test --- subrootpaths.go | 40 ++++++++++++++-------------------------- subrootpaths_test.go | 2 ++ 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/subrootpaths.go b/subrootpaths.go index c6148320..8fc9ad7d 100644 --- a/subrootpaths.go +++ b/subrootpaths.go @@ -22,10 +22,10 @@ func subdivide(idxStart uint, width uint) []int { return path } -func extractBranch(path []int, depth int, index int, offset int) []int { +func extractBranch(path []int, depth int, index int, offset int, branch int) []int { rightCapture := make([]int, len(path)) copy(rightCapture, path) - rightCapture[len(path)-index] = 1 + rightCapture[len(path)-index] = branch return rightCapture[:depth-(index-offset)] } @@ -60,8 +60,10 @@ func prune(idxStart uint, pathStart []int, idxEnd uint, pathEnd []int, maxWidth treeDepth := len(pathStart) capturedSpan := uint(0) rightTraversed := false + // lastVisited := 0 for i := 1; i <= treeDepth; i++ { + // lastVisited = i nodeSpan := uint(math.Pow(float64(2), float64(i))) if pathStart[len(pathStart)-i] == 0 { // if nodespan is less than end index, continue traversing upwards @@ -70,15 +72,15 @@ func prune(idxStart uint, pathStart []int, idxEnd uint, pathEnd []int, maxWidth // if a right path has been encountered, we want to return the right // branch one level down if rightTraversed { - prunedPaths = append(prunedPaths, extractBranch(pathStart, treeDepth, i, 1)) + prunedPaths = append(prunedPaths, extractBranch(pathStart, treeDepth, i, 1, 1)) } else { // else add the current root node - prunedPaths = append(prunedPaths, extractBranch(pathStart, treeDepth, i, 0)) + prunedPaths = append(prunedPaths, extractBranch(pathStart, treeDepth, i, 0, 1)) } } else if (nodeSpan+idxStart)-1 == idxEnd { // if it's equal to the end index, this is the final root to return if rightTraversed { - prunedPaths = append(prunedPaths, extractBranch(pathStart, treeDepth, i, 1)) + prunedPaths = append(prunedPaths, extractBranch(pathStart, treeDepth, i, 1, 1)) return append(preprocessedPaths, prunedPaths...) } else { // if we've never traversed right then this is a special case @@ -88,6 +90,9 @@ func prune(idxStart uint, pathStart []int, idxEnd uint, pathEnd []int, maxWidth } else { // else if it's greater than the end index, break out of the left-capture loop capturedSpan = nodeSpan/2 - 1 + if !rightTraversed { + prunedPaths = append([][]int{}, prunedPaths[len(prunedPaths)-1]) + } break } } else { @@ -100,27 +105,10 @@ func prune(idxStart uint, pathStart []int, idxEnd uint, pathEnd []int, maxWidth } } - var outPath []int - - for i := 1; i <= treeDepth; i++ { - // if we ever reach a left branch connection on this loop we've found the final slice - if pathEnd[len(pathEnd)-i] == 0 { - if outPath == nil { - outPath = pathEnd[:len(pathEnd)-(i-1)] - } - break - } else { - nodeSpan := uint(math.Pow(float64(2), float64(i))) - if int(idxEnd)-int(nodeSpan) <= int(capturedSpan) { - // traverse upwards while updating the latest path found - outPath = extractBranch(pathEnd, treeDepth, i, 0) - } - } - } - - prunedPaths = append(prunedPaths, outPath) - - return append(preprocessedPaths, prunedPaths...) + combined := append(preprocessedPaths, prunedPaths...) + newStart := idxStart + capturedSpan + 1 + return append(combined, prune(newStart, subdivide(newStart, maxWidth), idxEnd, pathEnd, maxWidth)...) + //return append(preprocessedPaths, prunedPaths...) } // GetSubrootPaths is a pure function that takes arguments: square size, share index start, diff --git a/subrootpaths_test.go b/subrootpaths_test.go index 14094f26..1e5546bc 100644 --- a/subrootpaths_test.go +++ b/subrootpaths_test.go @@ -41,11 +41,13 @@ func TestPathGeneration(t *testing.T) { {input: [3]uint{2, 1, 1}, want: [][][]int{{{1}}}}, {input: [3]uint{4, 1, 2}, want: [][][]int{{{0, 1}, {1, 0}}}}, {input: [3]uint{8, 1, 6}, want: [][][]int{{{0, 0, 1}, {1, 1, 0}, {0, 1}, {1, 0}}}}, + {input: [3]uint{32, 16, 16}, want: [][][]int{{{1}}}}, {input: [3]uint{32, 0, 32}, want: [][][]int{{{}}}}, {input: [3]uint{32, 0, 64}, want: [][][]int{{{}}, {{}}}}, {input: [3]uint{32, 0, 96}, want: [][][]int{{{}}, {{}}, {{}}}}, {input: [3]uint{32, 18, 11}, want: [][][]int{{{1, 1, 1, 0, 0}, {1, 0, 0, 1}, {1, 0, 1}, {1, 1, 0}}}}, {input: [3]uint{32, 14, 18}, want: [][][]int{{{0, 1, 1, 1}, {1}}}}, + {input: [3]uint{32, 14, 17}, want: [][][]int{{{1, 1, 1, 1, 0}, {0, 1, 1, 1}, {1, 0}, {1, 1, 0}, {1, 1, 1, 0}}}}, {input: [3]uint{32, 48, 16}, want: [][][]int{{{1}}}}, } From 80b59c5ee004659d59b649e90f63504de303d44d Mon Sep 17 00:00:00 2001 From: mattdf Date: Fri, 15 Oct 2021 17:15:35 +0200 Subject: [PATCH 11/19] clean up comments, add clarifying comment on new case --- subrootpaths.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/subrootpaths.go b/subrootpaths.go index 8fc9ad7d..c7085e35 100644 --- a/subrootpaths.go +++ b/subrootpaths.go @@ -60,10 +60,8 @@ func prune(idxStart uint, pathStart []int, idxEnd uint, pathEnd []int, maxWidth treeDepth := len(pathStart) capturedSpan := uint(0) rightTraversed := false - // lastVisited := 0 for i := 1; i <= treeDepth; i++ { - // lastVisited = i nodeSpan := uint(math.Pow(float64(2), float64(i))) if pathStart[len(pathStart)-i] == 0 { // if nodespan is less than end index, continue traversing upwards @@ -91,6 +89,8 @@ func prune(idxStart uint, pathStart []int, idxEnd uint, pathEnd []int, maxWidth // else if it's greater than the end index, break out of the left-capture loop capturedSpan = nodeSpan/2 - 1 if !rightTraversed { + // if a right path hasn't been encountered, add only the last node added + // as it will contain all the previous ones perfectly prunedPaths = append([][]int{}, prunedPaths[len(prunedPaths)-1]) } break @@ -108,7 +108,6 @@ func prune(idxStart uint, pathStart []int, idxEnd uint, pathEnd []int, maxWidth combined := append(preprocessedPaths, prunedPaths...) newStart := idxStart + capturedSpan + 1 return append(combined, prune(newStart, subdivide(newStart, maxWidth), idxEnd, pathEnd, maxWidth)...) - //return append(preprocessedPaths, prunedPaths...) } // GetSubrootPaths is a pure function that takes arguments: square size, share index start, From 94c4501c8b0f2c4957c2c834b22c9caa48ebaf51 Mon Sep 17 00:00:00 2001 From: Matthew Di Ferrante Date: Tue, 19 Oct 2021 22:37:26 +0200 Subject: [PATCH 12/19] more idiomatic errors Co-authored-by: Ismail Khoffi --- subrootpaths.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/subrootpaths.go b/subrootpaths.go index c7085e35..ce214d63 100644 --- a/subrootpaths.go +++ b/subrootpaths.go @@ -5,9 +5,11 @@ import ( "math" ) -var SRPNotPowerOf2 = errors.New("GetSubrootPaths: Supplied square size is not a power of 2") -var SRPInvalidShareSize = errors.New("GetSubrootPaths: Can't compute path for 0 length share slice") -var SRPPastSquareSize = errors.New("GetSubrootPaths: Share slice can't be past the square size") +var ( + SRPNotPowerOf2 = errors.New("GetSubrootPaths: Supplied square size is not a power of 2") + SRPInvalidShareSize = errors.New("GetSubrootPaths: Can't compute path for 0 length share slice") + SRPPastSquareSize = errors.New("GetSubrootPaths: Share slice can't be past the square size") +) func subdivide(idxStart uint, width uint) []int { var path []int From ea05d607814b0786234b821ec2a41dc35295cb42 Mon Sep 17 00:00:00 2001 From: mattdf Date: Thu, 21 Oct 2021 13:45:09 +0200 Subject: [PATCH 13/19] better test descriptions, lowercase errors, add entire-square test --- subrootpaths.go | 12 ++--- subrootpaths_test.go | 116 +++++++++++++++++++++++++++++++++---------- 2 files changed, 97 insertions(+), 31 deletions(-) diff --git a/subrootpaths.go b/subrootpaths.go index ce214d63..5e19bfa6 100644 --- a/subrootpaths.go +++ b/subrootpaths.go @@ -6,9 +6,9 @@ import ( ) var ( - SRPNotPowerOf2 = errors.New("GetSubrootPaths: Supplied square size is not a power of 2") - SRPInvalidShareSize = errors.New("GetSubrootPaths: Can't compute path for 0 length share slice") - SRPPastSquareSize = errors.New("GetSubrootPaths: Share slice can't be past the square size") + srpNotPowerOf2 = errors.New("GetSubrootPaths: Supplied square size is not a power of 2") + srpInvalidShareSize = errors.New("GetSubrootPaths: Can't compute path for 0 length share slice") + srpPastSquareSize = errors.New("GetSubrootPaths: Share slice can't be past the square size") ) func subdivide(idxStart uint, width uint) []int { @@ -128,12 +128,12 @@ func GetSubrootPaths(squareSize uint, idxStart uint, shareLen uint) ([][][]int, // check if squareSize is a power of 2 by checking that only 1 bit is on if squareSize < 2 || !((squareSize & (squareSize - 1)) == 0) { - return nil, SRPNotPowerOf2 + return nil, srpNotPowerOf2 } // no path exists for 0 length slice if shareLen == 0 { - return nil, SRPInvalidShareSize + return nil, srpInvalidShareSize } // adjust for 0 index @@ -141,7 +141,7 @@ func GetSubrootPaths(squareSize uint, idxStart uint, shareLen uint) ([][][]int, // sanity checking if idxStart >= shares || idxStart+shareLen >= shares { - return nil, SRPPastSquareSize + return nil, srpPastSquareSize } startRow := int(math.Floor(float64(idxStart) / float64(squareSize))) diff --git a/subrootpaths_test.go b/subrootpaths_test.go index 1e5546bc..ecca857d 100644 --- a/subrootpaths_test.go +++ b/subrootpaths_test.go @@ -5,23 +5,31 @@ import ( "testing" ) +type pathSpan struct { + squareSize uint + startNode uint + length uint +} + +type pathResult [][][]int + func TestArgValidation(t *testing.T) { type test struct { - input [3]uint + input pathSpan want error } tests := []test{ - {input: [3]uint{0, 0, 0}, want: SRPNotPowerOf2}, - {input: [3]uint{1, 0, 1}, want: SRPNotPowerOf2}, - {input: [3]uint{20, 0, 1}, want: SRPNotPowerOf2}, - {input: [3]uint{4, 0, 17}, want: SRPPastSquareSize}, - {input: [3]uint{4, 0, 0}, want: SRPInvalidShareSize}, + {input: pathSpan{squareSize: 0, startNode: 0, length: 0}, want: srpNotPowerOf2}, + {input: pathSpan{squareSize: 1, startNode: 0, length: 1}, want: srpNotPowerOf2}, + {input: pathSpan{squareSize: 20, startNode: 0, length: 1}, want: srpNotPowerOf2}, + {input: pathSpan{squareSize: 4, startNode: 0, length: 17}, want: srpPastSquareSize}, + {input: pathSpan{squareSize: 4, startNode: 0, length: 0}, want: srpInvalidShareSize}, } for _, tc := range tests { - paths, err := GetSubrootPaths(tc.input[0], tc.input[1], tc.input[2]) + paths, err := GetSubrootPaths(tc.input.squareSize, tc.input.startNode, tc.input.length) if err != tc.want { t.Fatalf(`GetSubrootPaths(%v) = %v, %v, want %v`, tc.input, paths, err, tc.want) } @@ -31,30 +39,88 @@ func TestArgValidation(t *testing.T) { func TestPathGeneration(t *testing.T) { type test struct { - input [3]uint - want [][][]int + input pathSpan + want pathResult + desc string } tests := []test{ - {input: [3]uint{2, 0, 2}, want: [][][]int{{{}}}}, - {input: [3]uint{2, 0, 1}, want: [][][]int{{{0}}}}, - {input: [3]uint{2, 1, 1}, want: [][][]int{{{1}}}}, - {input: [3]uint{4, 1, 2}, want: [][][]int{{{0, 1}, {1, 0}}}}, - {input: [3]uint{8, 1, 6}, want: [][][]int{{{0, 0, 1}, {1, 1, 0}, {0, 1}, {1, 0}}}}, - {input: [3]uint{32, 16, 16}, want: [][][]int{{{1}}}}, - {input: [3]uint{32, 0, 32}, want: [][][]int{{{}}}}, - {input: [3]uint{32, 0, 64}, want: [][][]int{{{}}, {{}}}}, - {input: [3]uint{32, 0, 96}, want: [][][]int{{{}}, {{}}, {{}}}}, - {input: [3]uint{32, 18, 11}, want: [][][]int{{{1, 1, 1, 0, 0}, {1, 0, 0, 1}, {1, 0, 1}, {1, 1, 0}}}}, - {input: [3]uint{32, 14, 18}, want: [][][]int{{{0, 1, 1, 1}, {1}}}}, - {input: [3]uint{32, 14, 17}, want: [][][]int{{{1, 1, 1, 1, 0}, {0, 1, 1, 1}, {1, 0}, {1, 1, 0}, {1, 1, 1, 0}}}}, - {input: [3]uint{32, 48, 16}, want: [][][]int{{{1}}}}, + { + input: pathSpan{squareSize: 2, startNode: 0, length: 2}, + want: pathResult{{{}}}, + desc: "Single row span, should return empty to signify one row root", + }, + { + input: pathSpan{squareSize: 2, startNode: 0, length: 1}, + want: pathResult{{{0}}}, + desc: "Single left-most node span, should return left-most branch", + }, + { + input: pathSpan{squareSize: 2, startNode: 1, length: 1}, + want: pathResult{{{1}}}, + desc: "Single right-most node span on first row, should return single-row right-most branch", + }, + { + input: pathSpan{squareSize: 4, startNode: 1, length: 2}, + want: pathResult{{{0, 1}, {1, 0}}}, + desc: "2-node span on unaligned start, should return two branch paths leading to two nodes in the middle of first row's tree", + }, + { + input: pathSpan{squareSize: 8, startNode: 1, length: 6}, + want: pathResult{{{0, 0, 1}, {1, 1, 0}, {0, 1}, {1, 0}}}, + desc: "Single row span, taking whole row minus start and end nodes, unaligned start and end. Should return two offset paths, two internal paths, in one row", + }, + { + input: pathSpan{squareSize: 32, startNode: 16, length: 16}, + want: pathResult{{{1}}}, + desc: "Single row span, taking the right half of the first row, should return right (1) branch of one row", + }, + { + input: pathSpan{squareSize: 32, startNode: 0, length: 32}, + want: pathResult{{{}}}, + desc: "Whole row span of a larger square, should return empty to signify one row root", + }, + { + input: pathSpan{squareSize: 32, startNode: 0, length: 64}, + want: pathResult{{{}}, {{}}}, + desc: "Whole row span of 2 rows, should return two empty lists to signify two row roots", + }, + { + input: pathSpan{squareSize: 32, startNode: 0, length: 96}, + want: pathResult{{{}}, {{}}, {{}}}, + desc: "Whole row span of 3 rows, should return three empty lists to signify three row roots", + }, + { + input: pathSpan{squareSize: 32, startNode: 18, length: 11}, + want: pathResult{{{1, 1, 1, 0, 0}, {1, 0, 0, 1}, {1, 0, 1}, {1, 1, 0}}}, + desc: "Span starting on right side of first row's tree, on an even-index start but not on a power-of-two alignment, ending on an even-index. Should return 4 paths: branch spanning 18-19, branch spanning 20-23, branch spanning 24-28, and single-node path to 29", + }, + { + input: pathSpan{squareSize: 32, startNode: 14, length: 18}, + want: pathResult{{{0, 1, 1, 1}, {1}}}, + desc: "Span starting on left side of first row's tree, spanning until end of tree. Should return two paths in one row: right-most branch on left side of tree, and whole right side of tree", + }, + { + input: pathSpan{squareSize: 32, startNode: 14, length: 17}, + want: pathResult{{{1, 1, 1, 1, 0}, {0, 1, 1, 1}, {1, 0}, {1, 1, 0}, {1, 1, 1, 0}}}, + desc: "Span starting on the last branch of the left side of the first row's tree, starting on an even index, ending at the second-to-last branch of the first row's tree, on an even index. Should return 5 paths: branch spanning 14-15, branch spanning 16-23, branch spanning 24-27, branch spanning 28-29, single-node path to 30", + }, + { + input: pathSpan{squareSize: 32, startNode: 48, length: 16}, + want: pathResult{{{1}}}, + desc: "Span for right side of second row in square. Should return a single branch in a single list, pointing to the first right path of the row within that starting index", + }, + { + input: pathSpan{squareSize: 32, startNode: 0, length: 1024}, + want: pathResult{{{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}}, + desc: "Span for the entire square. Should return 32 empty lists to signify span covers every row in the square", + }, } for _, tc := range tests { - paths, err := GetSubrootPaths(tc.input[0], tc.input[1], tc.input[2]) - if !reflect.DeepEqual(paths, tc.want) { - t.Fatalf(`GetSubrootPaths(%v) = %v, %v, want %v`, tc.input, paths, err, tc.want) + paths, err := GetSubrootPaths(tc.input.squareSize, tc.input.startNode, tc.input.length) + if !reflect.DeepEqual(pathResult(paths), tc.want) { + t.Fatalf(`GetSubrootPaths(%v) = %v, %v, want %v - rationale: %v`, tc.input, paths, err, tc.want, tc.desc) } } From 47048c57dbf900f01288374755747a3c440c261d Mon Sep 17 00:00:00 2001 From: mattdf Date: Thu, 21 Oct 2021 14:39:16 +0200 Subject: [PATCH 14/19] add infinite loop bug in case of submitting a span for last two rows --- subrootpaths.go | 2 +- subrootpaths_test.go | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/subrootpaths.go b/subrootpaths.go index 5e19bfa6..3053fcfc 100644 --- a/subrootpaths.go +++ b/subrootpaths.go @@ -165,7 +165,7 @@ func GetSubrootPaths(squareSize uint, idxStart uint, shareLen uint) ([][][]int, // if the shares span multiple rows, treat it as 2 different path generations, // one from left-most root to end of a row, and one from start of a row to right-most root, // and returning nil lists for the fully covered rows in between= - left, _ := GetSubrootPaths(squareSize, idxStart, squareSize-idxStart) + left, _ := GetSubrootPaths(squareSize, shareStart, squareSize-shareStart) right, _ := GetSubrootPaths(squareSize, 0, shareEnd+1) top = append(top, left[0]) for i := 1; i < (endRow-startRow)-1; i++ { diff --git a/subrootpaths_test.go b/subrootpaths_test.go index ecca857d..edf16a96 100644 --- a/subrootpaths_test.go +++ b/subrootpaths_test.go @@ -1,6 +1,7 @@ package nmt import ( + "fmt" "reflect" "testing" ) @@ -115,11 +116,22 @@ func TestPathGeneration(t *testing.T) { want: pathResult{{{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}}, desc: "Span for the entire square. Should return 32 empty lists to signify span covers every row in the square", }, + { + input: pathSpan{squareSize: 32, startNode: 988, length: 32}, + want: pathResult{{{1, 1, 1}}, {{0}, {1, 0}, {1, 1, 0}}}, + desc: "Span for last two rows in square, should return last branch of second to last row, left half of last row, and two branches on right half of last row", + }, + { + input: pathSpan{squareSize: 32, startNode: 992, length: 32}, + want: pathResult{{{}}}, + desc: "Span for last row in the square, should return empty list.", + }, } for _, tc := range tests { paths, err := GetSubrootPaths(tc.input.squareSize, tc.input.startNode, tc.input.length) if !reflect.DeepEqual(pathResult(paths), tc.want) { + fmt.Println(tc.desc) t.Fatalf(`GetSubrootPaths(%v) = %v, %v, want %v - rationale: %v`, tc.input, paths, err, tc.want, tc.desc) } } From 3acd307bb141ba63b10cbc71dfdc9040dd9fa882 Mon Sep 17 00:00:00 2001 From: mattdf Date: Thu, 21 Oct 2021 14:40:14 +0200 Subject: [PATCH 15/19] remove trailing debug statement --- subrootpaths_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/subrootpaths_test.go b/subrootpaths_test.go index edf16a96..509d8a17 100644 --- a/subrootpaths_test.go +++ b/subrootpaths_test.go @@ -1,7 +1,6 @@ package nmt import ( - "fmt" "reflect" "testing" ) @@ -131,7 +130,6 @@ func TestPathGeneration(t *testing.T) { for _, tc := range tests { paths, err := GetSubrootPaths(tc.input.squareSize, tc.input.startNode, tc.input.length) if !reflect.DeepEqual(pathResult(paths), tc.want) { - fmt.Println(tc.desc) t.Fatalf(`GetSubrootPaths(%v) = %v, %v, want %v - rationale: %v`, tc.input, paths, err, tc.want, tc.desc) } } From 9c8ffd48bcbdc3575f11f1049dd1245403dc7c9c Mon Sep 17 00:00:00 2001 From: mattdf Date: Thu, 21 Oct 2021 16:03:49 +0200 Subject: [PATCH 16/19] significantly simplify implementation, use cleaner code --- subrootpaths.go | 61 +++++++++++++++++++------------------------- subrootpaths_test.go | 5 ++++ 2 files changed, 31 insertions(+), 35 deletions(-) diff --git a/subrootpaths.go b/subrootpaths.go index 3053fcfc..04a12669 100644 --- a/subrootpaths.go +++ b/subrootpaths.go @@ -24,18 +24,21 @@ func subdivide(idxStart uint, width uint) []int { return path } -func extractBranch(path []int, depth int, index int, offset int, branch int) []int { +func extractBranch(path []int, index int, offset int, branch int) []int { rightCapture := make([]int, len(path)) copy(rightCapture, path) - rightCapture[len(path)-index] = branch - return rightCapture[:depth-(index-offset)] + rightCapture[index] = branch + return rightCapture[:index+offset] } -func prune(idxStart uint, pathStart []int, idxEnd uint, pathEnd []int, maxWidth uint) [][]int { +func prune(idxStart uint, idxEnd uint, maxWidth uint) [][]int { var prunedPaths [][]int var preprocessedPaths [][]int + pathStart := subdivide(idxStart, maxWidth) + pathEnd := subdivide(idxEnd, maxWidth) + // special case of two-share length, just return one or two paths if idxStart+1 >= idxEnd { if idxStart%2 == 1 { @@ -63,38 +66,23 @@ func prune(idxStart uint, pathStart []int, idxEnd uint, pathEnd []int, maxWidth capturedSpan := uint(0) rightTraversed := false - for i := 1; i <= treeDepth; i++ { - nodeSpan := uint(math.Pow(float64(2), float64(i))) - if pathStart[len(pathStart)-i] == 0 { + for i := treeDepth - 1; i >= 0 && capturedSpan < idxEnd; i-- { + nodeSpan := uint(math.Pow(float64(2), float64(treeDepth-i))) + if pathStart[i] == 0 { // if nodespan is less than end index, continue traversing upwards - if (nodeSpan+idxStart)-1 < idxEnd { - capturedSpan = nodeSpan + lastNode := nodeSpan + idxStart - 1 + if lastNode <= idxEnd { + capturedSpan = lastNode // if a right path has been encountered, we want to return the right // branch one level down if rightTraversed { - prunedPaths = append(prunedPaths, extractBranch(pathStart, treeDepth, i, 1, 1)) - } else { - // else add the current root node - prunedPaths = append(prunedPaths, extractBranch(pathStart, treeDepth, i, 0, 1)) - } - } else if (nodeSpan+idxStart)-1 == idxEnd { - // if it's equal to the end index, this is the final root to return - if rightTraversed { - prunedPaths = append(prunedPaths, extractBranch(pathStart, treeDepth, i, 1, 1)) - return append(preprocessedPaths, prunedPaths...) + prunedPaths = append(prunedPaths, extractBranch(pathStart, i, 1, 1)) } else { - // if we've never traversed right then this is a special case - // where the last root found here encompasses the whole lower tree - return append(preprocessedPaths, pathStart[:treeDepth-i]) + // else add *just* the current root node + prunedPaths = [][]int{pathStart[:i]} } } else { // else if it's greater than the end index, break out of the left-capture loop - capturedSpan = nodeSpan/2 - 1 - if !rightTraversed { - // if a right path hasn't been encountered, add only the last node added - // as it will contain all the previous ones perfectly - prunedPaths = append([][]int{}, prunedPaths[len(prunedPaths)-1]) - } break } } else { @@ -107,9 +95,15 @@ func prune(idxStart uint, pathStart []int, idxEnd uint, pathEnd []int, maxWidth } } + // if the process captured the span to the end, return the results + if capturedSpan == idxEnd { + return append(preprocessedPaths, prunedPaths...) + } + + // else recurse into the leftover span combined := append(preprocessedPaths, prunedPaths...) - newStart := idxStart + capturedSpan + 1 - return append(combined, prune(newStart, subdivide(newStart, maxWidth), idxEnd, pathEnd, maxWidth)...) + newStart := capturedSpan + 1 + return append(combined, prune(newStart, idxEnd, maxWidth)...) } // GetSubrootPaths is a pure function that takes arguments: square size, share index start, @@ -150,17 +144,14 @@ func GetSubrootPaths(squareSize uint, idxStart uint, shareLen uint) ([][][]int, shareStart := idxStart % squareSize shareEnd := (idxStart + shareLen) % squareSize - pathStart := subdivide(shareStart, squareSize) - pathEnd := subdivide(shareEnd, squareSize) - // if the length is one, just return the subdivided start path if shareLen == 0 { - return append(top, append(paths, pathStart)), nil + return append(top, append(paths, subdivide(shareStart, squareSize))), nil } // if the shares are all in one row, do the normal case if startRow == endRow-1 { - top = append(top, prune(shareStart, pathStart, shareEnd, pathEnd, squareSize)) + top = append(top, prune(shareStart, shareEnd, squareSize)) } else { // if the shares span multiple rows, treat it as 2 different path generations, // one from left-most root to end of a row, and one from start of a row to right-most root, diff --git a/subrootpaths_test.go b/subrootpaths_test.go index 509d8a17..8ebb487f 100644 --- a/subrootpaths_test.go +++ b/subrootpaths_test.go @@ -125,6 +125,11 @@ func TestPathGeneration(t *testing.T) { want: pathResult{{{}}}, desc: "Span for last row in the square, should return empty list.", }, + { + input: pathSpan{squareSize: 32, startNode: 1023, length: 1}, + want: pathResult{{{1, 1, 1, 1, 1}}}, + desc: "Span for last node in the last row in the square, should return a path of 1s", + }, } for _, tc := range tests { From bd1b671d1ceaa84ef31dee76c5c53afe43a1e20d Mon Sep 17 00:00:00 2001 From: mattdf Date: Thu, 21 Oct 2021 16:07:37 +0200 Subject: [PATCH 17/19] remove ineffectual assign to pass go lint --- subrootpaths.go | 1 - 1 file changed, 1 deletion(-) diff --git a/subrootpaths.go b/subrootpaths.go index 04a12669..245f7bd5 100644 --- a/subrootpaths.go +++ b/subrootpaths.go @@ -59,7 +59,6 @@ func prune(idxStart uint, idxEnd uint, maxWidth uint) [][]int { if idxEnd%2 == 0 { idxEnd-- preprocessedPaths = append(preprocessedPaths, pathEnd) - pathEnd = subdivide(idxEnd, maxWidth) } treeDepth := len(pathStart) From f67f8cde2c8d1170cbab636d4250746dbd8f1d7c Mon Sep 17 00:00:00 2001 From: mattdf Date: Thu, 21 Oct 2021 16:21:19 +0200 Subject: [PATCH 18/19] final cleanup --- subrootpaths.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/subrootpaths.go b/subrootpaths.go index 245f7bd5..4cf77d01 100644 --- a/subrootpaths.go +++ b/subrootpaths.go @@ -42,9 +42,9 @@ func prune(idxStart uint, idxEnd uint, maxWidth uint) [][]int { // special case of two-share length, just return one or two paths if idxStart+1 >= idxEnd { if idxStart%2 == 1 { - return append(prunedPaths, pathStart, pathEnd) + return [][]int{pathStart, pathEnd} } else { - return append(prunedPaths, pathStart[:len(pathStart)-1]) + return [][]int{pathStart[:len(pathStart)-1]} } } @@ -94,15 +94,13 @@ func prune(idxStart uint, idxEnd uint, maxWidth uint) [][]int { } } + combined := append(preprocessedPaths, prunedPaths...) // if the process captured the span to the end, return the results if capturedSpan == idxEnd { - return append(preprocessedPaths, prunedPaths...) + return combined } - // else recurse into the leftover span - combined := append(preprocessedPaths, prunedPaths...) - newStart := capturedSpan + 1 - return append(combined, prune(newStart, idxEnd, maxWidth)...) + return append(combined, prune(capturedSpan+1, idxEnd, maxWidth)...) } // GetSubrootPaths is a pure function that takes arguments: square size, share index start, From fee9337d89554d57f155963ce039be41786a4cdd Mon Sep 17 00:00:00 2001 From: mattdf Date: Thu, 21 Oct 2021 18:10:35 +0200 Subject: [PATCH 19/19] add fixes suggested by john --- subrootpaths.go | 50 +++++++++++++++++++++++++------------------- subrootpaths_test.go | 2 +- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/subrootpaths.go b/subrootpaths.go index 4cf77d01..d9f5f208 100644 --- a/subrootpaths.go +++ b/subrootpaths.go @@ -3,14 +3,17 @@ package nmt import ( "errors" "math" + "math/bits" ) var ( - srpNotPowerOf2 = errors.New("GetSubrootPaths: Supplied square size is not a power of 2") - srpInvalidShareSize = errors.New("GetSubrootPaths: Can't compute path for 0 length share slice") - srpPastSquareSize = errors.New("GetSubrootPaths: Share slice can't be past the square size") + srpNotPowerOf2 = errors.New("GetSubrootPaths: Supplied square size is not a power of 2") + srpInvalidShareCount = errors.New("GetSubrootPaths: Can't compute path for 0 share count slice") + srpPastSquareSize = errors.New("GetSubrootPaths: Share slice can't be past the square size") ) +// merkle path to a node is equivalent to the index's binary representation +// this is just a quick function to return that representation as a list of ints func subdivide(idxStart uint, width uint) []int { var path []int pathlen := int(math.Log2(float64(width))) @@ -24,6 +27,8 @@ func subdivide(idxStart uint, width uint) []int { return path } +// this function takes a path, and returns a copy of that path with path[index] set to branch, +// and cuts off the list at path[:index+offset] - used to create inclusion branches during traversal func extractBranch(path []int, index int, offset int, branch int) []int { rightCapture := make([]int, len(path)) copy(rightCapture, path) @@ -39,7 +44,7 @@ func prune(idxStart uint, idxEnd uint, maxWidth uint) [][]int { pathStart := subdivide(idxStart, maxWidth) pathEnd := subdivide(idxEnd, maxWidth) - // special case of two-share length, just return one or two paths + // special case of two-share path, just return one or two paths if idxStart+1 >= idxEnd { if idxStart%2 == 1 { return [][]int{pathStart, pathEnd} @@ -104,59 +109,60 @@ func prune(idxStart uint, idxEnd uint, maxWidth uint) [][]int { } // GetSubrootPaths is a pure function that takes arguments: square size, share index start, -// and share length, and returns a minimal set of paths to the subtree roots that +// and share Count, and returns a minimal set of paths to the subtree roots that // encompasses that entire range of shares, with each top level entry in the list // starting from the nearest row root. // // An empty entry in the top level list means the shares span that entire row and so // the root for that segment of shares is equivalent to the row root. -func GetSubrootPaths(squareSize uint, idxStart uint, shareLen uint) ([][][]int, error) { +func GetSubrootPaths(squareSize uint, idxStart uint, shareCount uint) ([][][]int, error) { var paths [][]int var top [][][]int shares := squareSize * squareSize - // check if squareSize is a power of 2 by checking that only 1 bit is on - if squareSize < 2 || !((squareSize & (squareSize - 1)) == 0) { + // check squareSize is at least 2 and that it's + // a power of 2 by checking that only 1 bit is on + if squareSize < 2 || bits.OnesCount(squareSize) != 1 { return nil, srpNotPowerOf2 } - // no path exists for 0 length slice - if shareLen == 0 { - return nil, srpInvalidShareSize + // no path exists for 0 count slice + if shareCount == 0 { + return nil, srpInvalidShareCount } - // adjust for 0 index - shareLen = shareLen - 1 - // sanity checking - if idxStart >= shares || idxStart+shareLen >= shares { + if idxStart >= shares || idxStart+shareCount > shares { return nil, srpPastSquareSize } + // adjust for 0 index + shareCount = shareCount - 1 + startRow := int(math.Floor(float64(idxStart) / float64(squareSize))) - endRow := int(math.Ceil(float64(idxStart+shareLen) / float64(squareSize))) + closingRow := int(math.Ceil(float64(idxStart+shareCount) / float64(squareSize))) shareStart := idxStart % squareSize - shareEnd := (idxStart + shareLen) % squareSize + shareEnd := (idxStart + shareCount) % squareSize - // if the length is one, just return the subdivided start path - if shareLen == 0 { + // if the count is one, just return the subdivided start path + if shareCount == 0 { return append(top, append(paths, subdivide(shareStart, squareSize))), nil } // if the shares are all in one row, do the normal case - if startRow == endRow-1 { + if startRow == closingRow-1 { top = append(top, prune(shareStart, shareEnd, squareSize)) } else { // if the shares span multiple rows, treat it as 2 different path generations, // one from left-most root to end of a row, and one from start of a row to right-most root, - // and returning nil lists for the fully covered rows in between= + // and returning nil lists for the fully covered rows in between left, _ := GetSubrootPaths(squareSize, shareStart, squareSize-shareStart) right, _ := GetSubrootPaths(squareSize, 0, shareEnd+1) top = append(top, left[0]) - for i := 1; i < (endRow-startRow)-1; i++ { + for i := 1; i < (closingRow-startRow)-1; i++ { top = append(top, [][]int{{}}) } top = append(top, right[0]) diff --git a/subrootpaths_test.go b/subrootpaths_test.go index 8ebb487f..5424bfcc 100644 --- a/subrootpaths_test.go +++ b/subrootpaths_test.go @@ -25,7 +25,7 @@ func TestArgValidation(t *testing.T) { {input: pathSpan{squareSize: 1, startNode: 0, length: 1}, want: srpNotPowerOf2}, {input: pathSpan{squareSize: 20, startNode: 0, length: 1}, want: srpNotPowerOf2}, {input: pathSpan{squareSize: 4, startNode: 0, length: 17}, want: srpPastSquareSize}, - {input: pathSpan{squareSize: 4, startNode: 0, length: 0}, want: srpInvalidShareSize}, + {input: pathSpan{squareSize: 4, startNode: 0, length: 0}, want: srpInvalidShareCount}, } for _, tc := range tests {