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

Subrootpaths #49

Merged
merged 19 commits into from
Oct 21, 2021
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 187 additions & 0 deletions subrootpaths.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package nmt

import (
"errors"
"math"
)

func subdivide(idxStart uint, width uint) []int {
mattdf marked this conversation as resolved.
Show resolved Hide resolved
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 extractBranch(path []int, depth int, index int, offset int) []int {
rightCapture := make([]int, len(path))
copy(rightCapture, path)
rightCapture[len(path)-index] = 1
return rightCapture[:depth-(index-offset)]
}

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, just return one or two paths
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, 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, add that single index and shift it left 1
if idxEnd%2 == 0 {
idxEnd -= 1
preprocessedPaths = append(preprocessedPaths, 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 is less than end index, continue traversing upwards
if (nodeSpan+idxStart)-1 < idxEnd {
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 {
// 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))
return append(preprocessedPaths, prunedPaths...)
} 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 {
// 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) {
// traverse upwards while updating the latest path found
outPath = extractBranch(pathEnd, treeDepth, i, 0)
}
}
}

prunedPaths = append(prunedPaths, outPath)

return append(preprocessedPaths, prunedPaths...)
}

// 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) {
mattdf marked this conversation as resolved.
Show resolved Hide resolved

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) {
mattdf marked this conversation as resolved.
Show resolved Hide resolved
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")
}

// adjust for 0 index
shareLen = shareLen - 1
mattdf marked this conversation as resolved.
Show resolved Hide resolved

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

// if the length is one, just return the subdivided start path
if shareLen == 0 {
return append(top, append(paths, pathStart)), nil
}

// if the shares are all in one row, do the normal case
if startRow == endRow-1 {
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
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=
mattdf marked this conversation as resolved.
Show resolved Hide resolved
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{{}})
}
top = append(top, right[0])
}

return top, nil
}
130 changes: 130 additions & 0 deletions subrootpaths_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package nmt

import (
"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) {

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(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}}}
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, 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)
}
}
}
liamsi marked this conversation as resolved.
Show resolved Hide resolved