Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
139 changes: 132 additions & 7 deletions cmd/proofgen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -531,8 +531,10 @@ func consistencyProbes(rootDir string) error {
return err
}

if err := staticConsistencyProbes(staticDir); err != nil {
return err
for _, p := range staticConsistencyProbes() {
if err := writeConsistencyProbe(staticDir, p); err != nil {
return err
}
}

return nil
Expand Down Expand Up @@ -604,13 +606,13 @@ func corruptedConsistencyProbes(dir string, size1, size2 uint64, proof [][]byte,
return nil
}

func staticConsistencyProbes(dir string) error {
func staticConsistencyProbes() []consistencyProbe {
root1 := []byte("don't care 1")
root2 := []byte("don't care 2")
proof1 := [][]byte{}
proof2 := [][]byte{sha256EmptyTreeHash}

tests := []consistencyProbe{
return []consistencyProbe{
{0, 0, root1, root2, proof1, "sizes are equal (zero) but roots are not", true},
{1, 1, root1, root2, proof1, "sizes are equal (one) but roots are not", true},
{0, 0, root1, root1, proof1, "sizes are equal (zero) and proof is empty", true},
Expand All @@ -632,17 +634,135 @@ func staticConsistencyProbes(dir string) error {
{0, 1, sha256EmptyTreeHash, root2, proof1, "size1 is zero and size2 is not zero", true},
{0, 1, sha256EmptyTreeHash, sha256EmptyTreeHash, proof2, "consistency check on empty tree (size1 is zero) is useless", true},
}
}

for _, p := range tests {
if err := writeConsistencyProbe(dir, p); err != nil {
func writeConsistencyProbe(dir string, probe consistencyProbe) error {
fn := fileName(probe.Desc)

probeJson, err := json.MarshalIndent(probe, "", " ")
if err != nil {
return fmt.Errorf("marshaling probe: %s", err)
}

fileLocation := filepath.Join(dir, fn)
if err := os.WriteFile(fileLocation, probeJson, 0644); err != nil {
return fmt.Errorf("writing probe: %s: %s", fn, err)
}

return nil
}

// =============================================================================
// Subtree Consistency Proofs
// =============================================================================

// subtreeConsistencyProbe is a parameter set for subtree consistency proof
// verification.
type subtreeConsistencyProbe struct {
Start uint64 `json:"start"`
End uint64 `json:"end"`
Size uint64 `json:"size"`
Root1 []byte `json:"root1"`
Root2 []byte `json:"root2"`
Proof [][]byte `json:"proof"`

Desc string `json:"desc"`
WantError bool `json:"wantErr"`
}

func subtreeConsistencyProbes(rootDir string) error {
for i, p := range consistencyProofs {
dir := filepath.Join(rootDir, strconv.Itoa(i))
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}

if err := corruptedSubtreeConsistencyProbes(dir, p.size1, p.size2, p.proof,
roots[p.size1-1], roots[p.size2-1]); err != nil {
return fmt.Errorf("write subtree consistency test data: %s", err)
}
}

staticDir := filepath.Join(rootDir, "additional")
if err := os.MkdirAll(staticDir, 0755); err != nil {
return err
}

if err := staticSubtreeConsistencyProbes(staticDir); err != nil {
return err
}

return nil
}

func corruptedSubtreeConsistencyProbes(dir string, size1, size2 uint64, proof [][]byte, root1, root2 []byte) error {
happyPath := subtreeConsistencyProbe{0, size1, size2, root1, root2, proof, "happy path", false}
if err := writeSubtreeConsistencyProbe(dir, happyPath); err != nil {
return err
}

if len(proof) == 0 {
return nil
}

probes := invalidSubtreeConsistencyProof(size1, size2, root1, root2, proof)
for _, p := range probes {
if err := writeSubtreeConsistencyProbe(dir, p); err != nil {
return err
}
}

return nil
}

func writeConsistencyProbe(dir string, probe consistencyProbe) error {
func invalidSubtreeConsistencyProof(size1, size2 uint64, root1, root2 []byte, proof [][]byte) []subtreeConsistencyProbe {
cProbes := invalidConsistencyProof(size1, size2, root1, root2, proof)
ret := make([]subtreeConsistencyProbe, 0, len(cProbes)+1)
for _, p := range cProbes {
ret = append(ret, subtreeConsistencyProbe{
Start: 0,
End: p.Size1,
Size: p.Size2,
Root1: p.Root1,
Root2: p.Root2,
Proof: p.Proof,
Desc: p.Desc,
WantError: p.WantError,
})
}
ret = append(ret, subtreeConsistencyProbe{
Start: 1,
End: 15,
Size: 15,
Root1: root1,
Root2: root2,
Proof: proof,
Desc: "invalid subtree",
WantError: true,
})
return ret
}

func staticSubtreeConsistencyProbes(dir string) error {
for _, p := range staticConsistencyProbes() {
sp := subtreeConsistencyProbe{
Start: 0,
End: p.Size1,
Size: p.Size2,
Root1: p.Root1,
Root2: p.Root2,
Proof: p.Proof,
Desc: p.Desc,
WantError: p.WantError,
}
if err := writeSubtreeConsistencyProbe(dir, sp); err != nil {
return err
}
}
return nil
}

func writeSubtreeConsistencyProbe(dir string, probe subtreeConsistencyProbe) error {
fn := fileName(probe.Desc)

probeJson, err := json.MarshalIndent(probe, "", " ")
Expand Down Expand Up @@ -710,4 +830,9 @@ func main() {
if err := consistencyProbes(consistencyDir); err != nil {
log.Fatalf("writing consistency test data: %s", err)
}

subtreeConsistencyDir := "testdata/subtreeconsistency"
if err := subtreeConsistencyProbes(subtreeConsistencyDir); err != nil {
log.Fatalf("writing subtree consistency test data: %s", err)
}
}
66 changes: 66 additions & 0 deletions proof/verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,20 @@ type consistencyProbe struct {
WantError bool `json:"wantErr"`
}

// subtreeConsistencyProbe is a parameter set for subtree consistency proof
// verification.
type subtreeConsistencyProbe struct {
Start uint64 `json:"start"`
End uint64 `json:"end"`
Size uint64 `json:"size"`
Root1 []byte `json:"root1"`
Root2 []byte `json:"root2"`
Proof [][]byte `json:"proof"`

Desc string `json:"desc"`
WantError bool `json:"wantErr"`
}

func TestVerifyInclusionProbes(t *testing.T) {
var probes []inclusionProbe

Expand Down Expand Up @@ -219,3 +233,55 @@ func TestVerifyConsistencyProbes(t *testing.T) {
t.Errorf("errors verifying consistency probes: \n%d out of %d failures \nError messages: \n%s", len(wrong), len(probes), strings.Join(wrong, "\n"))
}
}

func TestVerifySubtreeConsistencyProbes(t *testing.T) {
var probes []subtreeConsistencyProbe

if err := filepath.WalkDir("../testdata/subtreeconsistency", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}

if d.IsDir() {
return nil
}

if filepath.Ext(d.Name()) != ".json" {
return nil
}

data, err := os.ReadFile(path)
if err != nil {
return err
}

var probe subtreeConsistencyProbe
if err := json.Unmarshal(data, &probe); err != nil {
return fmt.Errorf("failed to parse subtree consistency probe json: %s", err)
}

probes = append(probes, probe)

return nil
}); err != nil {
t.Errorf("failed to read subtree consistency probes: %s", err)
}

var wrong []string
for _, p := range probes {
err := VerifySubtreeConsistency(rfc6962.DefaultHasher, p.Start, p.End, p.Size, p.Proof, p.Root1, p.Root2)
if p.WantError && err == nil {
wrong = append(wrong, fmt.Sprintf("expected error but didn't get one: %s", p.Desc))
continue
}

if !p.WantError && err != nil {
wrong = append(wrong, fmt.Sprintf("unexpected error: %s, %s", p.Desc, err))
continue
}
}

if len(wrong) > 0 {
t.Errorf("errors verifying subtree consistency probes: \n%d out of %d failures \nError messages: \n%s", len(wrong), len(probes), strings.Join(wrong, "\n"))
}
}
10 changes: 10 additions & 0 deletions testdata/subtreeconsistency/0/happy-path.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"start": 0,
"end": 1,
"size": 1,
"root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=",
"root2": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=",
"proof": null,
"desc": "happy path",
"wantErr": false
}
10 changes: 10 additions & 0 deletions testdata/subtreeconsistency/1/empty-proof.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"start": 0,
"end": 1,
"size": 8,
"root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=",
"root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=",
"proof": [],
"desc": "empty proof",
"wantErr": true
}
14 changes: 14 additions & 0 deletions testdata/subtreeconsistency/1/happy-path.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"start": 0,
"end": 1,
"size": 8,
"root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=",
"root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=",
"proof": [
"lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=",
"Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=",
"a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ="
],
"desc": "happy path",
"wantErr": false
}
14 changes: 14 additions & 0 deletions testdata/subtreeconsistency/1/invalid-subtree.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"start": 1,
"end": 15,
"size": 15,
"root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=",
"root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=",
"proof": [
"lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=",
"Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=",
"a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ="
],
"desc": "invalid subtree",
"wantErr": true
}
14 changes: 14 additions & 0 deletions testdata/subtreeconsistency/1/modified-proof@0-bit-@4.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"start": 0,
"end": 1,
"size": 8,
"root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=",
"root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=",
"proof": [
"hqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=",
"Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=",
"a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ="
],
"desc": "modified proof@0 bit @4",
"wantErr": true
}
14 changes: 14 additions & 0 deletions testdata/subtreeconsistency/1/modified-proof@1-bit-@4.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"start": 0,
"end": 1,
"size": 8,
"root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=",
"root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=",
"proof": [
"lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=",
"Twg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=",
"a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ="
],
"desc": "modified proof@1 bit @4",
"wantErr": true
}
14 changes: 14 additions & 0 deletions testdata/subtreeconsistency/1/modified-proof@2-bit-@4.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"start": 0,
"end": 1,
"size": 8,
"root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=",
"root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=",
"proof": [
"lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=",
"Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=",
"e0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ="
],
"desc": "modified proof@2 bit @4",
"wantErr": true
}
15 changes: 15 additions & 0 deletions testdata/subtreeconsistency/1/preceding-garbage.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"start": 0,
"end": 1,
"size": 8,
"root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=",
"root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=",
"proof": [
"",
"lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=",
"Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=",
"a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ="
],
"desc": "preceding garbage",
"wantErr": true
}
15 changes: 15 additions & 0 deletions testdata/subtreeconsistency/1/preceding-proof-@0.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"start": 0,
"end": 1,
"size": 8,
"root1": "bjQLnP+zepicpUTmu3gKLHiQHT+zNzh2hRGjBhevoB0=",
"root2": "XcnaeacGWamtVZy3Ad7ZoqudgjqtL0lgz+Nw7/RgQyg=",
"proof": [
"lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=",
"lqKW0iTyhcZ77pPDD4owkVfw2qNdxbh+QQt4YwoJz8c=",
"Xwg/ChozygdqlSeYMlgNs+DvRYS9/x9UyKNg9Q3jAx4=",
"a0eq8p7jwq+a+Im8H7klTavTEXfxYjLdaqsDXKOb9uQ="
],
"desc": "preceding proof @0",
"wantErr": true
}
Loading
Loading