// Verify verifies a prune proof for the provided arguments. Returns true if valid, // otherwise false. func (p *PruneProof) Verify(keys [][]byte, answer bool, root []byte) (valid bool) { // always possible to insert no keys if len(keys) == 0 { return answer // same as answer == true } // create a pruned hash treap out of all nodes in the proof proofTree := NewHashTreap() if len(p.Nodes) > 0 { // linearly build the proof tree // this involves no verification of the nodes proofTree.newPrunedHashTreap(p.Nodes) } if !util.Equal(root, proofTree.Root()) { return false } // go over each key, looking if it exists in the proof for _, k := range keys { value, valid := proofTree.verifiablyGet(k) // at least one invalid node in the proof if !valid { return false } // the key exists if value != nil { return !answer && util.Equal(k, p.Key) } } return answer }
// Update updates balloon with the slice of events, producing the next snapshot. // Run by the author on trusted input. func (balloon *Balloon) Update(events []Event, current *Snapshot, sk []byte) (next *Snapshot, err error) { if len(events) == 0 { return nil, errors.New("you need to add at least one event") } if !util.Equal(current.Roots.History, balloon.history.Root()) || !util.Equal(current.Roots.Treap, balloon.treap.Root()) { return nil, errors.New("provided snapshot is not current") } sort.Sort(ByKey(events)) // attempt to add events treap := balloon.treap ht := balloon.history.Clone() for i := 0; i < len(events); i++ { // add the hash of the entire event to the history tree _, err = ht.Add(util.Hash(append(events[i].Key, events[i].Value...))) if err != nil { return } // add to the treap the hash of the key pointing to the index (version) of the // hash of the event in the history tree treap, err = treap.Add(util.Hash(events[i].Key), util.Itob(ht.LatestVersion())) if err != nil { return } } // attempt to create next snapshot next = new(Snapshot) next.Index = current.Index + 1 next.Roots.History = ht.Root() next.Roots.Treap = treap.Root() next.Roots.Version = ht.LatestVersion() next.Previous = current.Signature signature, err := util.Sign(balloon.sk, append(append([]byte("snapshot"), next.Roots.History...), append(next.Roots.Treap, next.Previous...)...)) if err != nil { panic(err) } next.Signature = signature // all is OK, save result err = balloon.Storage.Store(events, *next) if err != nil { return nil, err } balloon.latestsnapshot = *next if len(events) > 0 { balloon.latesteventkey = events[len(events)-1].Key } balloon.treap = treap balloon.history = ht return }
// Equal determines if another snapshot is equal to this snapshot. func (snap *Snapshot) Equal(other *Snapshot) bool { if snap == nil || other == nil || snap.Index != other.Index || !util.Equal(snap.Signature, other.Signature) || !util.Equal(snap.Roots.History, other.Roots.History) || !util.Equal(snap.Roots.Treap, other.Roots.Treap) || snap.Roots.Version != other.Roots.Version { return false } return true }
// Verify verifies a membership query for a provided key from an expected // root hash that fixes a hash treap. Returns true if the proof is valid , // false otherwise. func (p *QueryProof) Verify(key, root []byte) (valid bool) { if len(p.Nodes) == 0 { // an empty hash treap shows non-membership for any key return p.Value == nil && root == nil } else if !util.Equal(p.Nodes[0].Hash, root) { return false } // build a pruned proofTree := NewHashTreap() proofTree.newPrunedHashTreap(p.Nodes) value, valid := proofTree.verifiablyGet(key) return valid && util.Equal(value, p.Value) && util.Equal(key, p.Key) }
func (t *HashTreap) verifiablyGet(key []byte) (value []byte, valid bool) { n := t.root for n != nil { // verify the node we are at var left, right []byte left = NoNodeHash right = NoNodeHash if n.left != nil { left = n.left.hash } if n.right != nil { right = n.right.hash } // verify the hash if !util.Equal(n.hash, util.Hash(n.key, n.value, left, right)) { return nil, false } c := bytes.Compare(key, n.key) if c < 0 { n = n.left } else if c > 0 { n = n.right } else { return n.value, true } } return nil, true }
// QueryPrune performs a prune query for a slice of events. Returns an answer indicating // if the events can be added and a proof. // Run by the server on untrusted input. The minimalProof flag trades a small amount // of computation for a significantly smaller proof (the more events, the bigger the reduction). func (balloon *Balloon) QueryPrune(events []Event, vk []byte, minimalProof bool) (answer bool, proof PruneProof) { // query hash treap treapKeys := make([][]byte, len(events)) for i := 0; i < len(events); i++ { treapKeys[i] = util.Hash(events[i].Key) } answer, proof.TreapProof = balloon.treap.QueryPrune(treapKeys, minimalProof) // if we found a member among the keys, then the proof is done shows that it is not // posssible to insert the set of events if !answer { return } // we only need/can extract the latest event, whose membership query fixes the history // tree, if there is at least one event in the Balloon if balloon.Size() > 0 { members, qevent, qproof, err := balloon.QueryMembership(balloon.latesteventkey, &balloon.latestsnapshot, vk) if err != nil { panic(err) } if !members { panic("an event that should be a member is not") } if !util.Equal(balloon.latesteventkey, qevent.Key) { panic("events differ") } proof.QueryProof = qproof proof.Event = *qevent } return }
// Verify verifies a proof and answer from QueryMembership. Returns true if the // answer and proof are correct and consistent, otherwise false. // Run by a client on input that should be verified. func (proof *QueryProof) Verify(key []byte, queried, current *Snapshot, answer bool, event *Event, vk []byte) (valid bool) { // verify the snapshots if !util.Verify(vk, append(append([]byte("snapshot"), queried.Roots.History...), append(queried.Roots.Treap, queried.Previous...)...), queried.Signature) { return false } if !util.Verify(vk, append(append([]byte("snapshot"), current.Roots.History...), append(current.Roots.Treap, current.Previous...)...), current.Signature) { return false } // check the authenticated path in the hash treap if !proof.TreapProof.Verify(util.Hash(key), current.Roots.Treap) { return false } // a non-membership proof where there is no event in the treap if !answer && proof.TreapProof.Value == nil && event == nil { return true } // a non-membership proof where the event was added _after_ the queried for snapshot index := util.Btoi(proof.TreapProof.Value) if !answer && index > queried.Roots.Version { return true } // a membership proof if answer && event != nil && util.Equal(event.Key, key) && proof.HistoryProof.Verify() && proof.HistoryProof.Index == index && proof.HistoryProof.Version == queried.Roots.Version && util.Equal(proof.HistoryProof.Root, queried.Roots.History) && util.Equal(proof.HistoryProof.Event, util.Hash(event.Key, event.Value)) { return true } // otherwise the proof is invalid return false }
func TestClone(t *testing.T) { // create a tree size := 128 tree := NewTree() events := make([][]byte, size) for i := 0; i < size; i++ { events[i] = []byte("event " + string(i)) _, err := tree.Add(events[i]) if err != nil { t.Fatalf("failed to add event: %s", err) } } // create a clone clone := tree.Clone() if !util.Equal(clone.Root(), tree.Root()) { t.Fatal("expected equal roots") } // update original tree for i := 0; i < size; i++ { events[i] = []byte("event " + string(i)) _, err := tree.Add(events[i]) if err != nil { t.Fatalf("failed to add event: %s", err) } } if util.Equal(clone.Root(), tree.Root()) { t.Fatal("expected different roots") } // update clone for i := 0; i < size; i++ { events[i] = []byte("event " + string(i)) _, err := clone.Add(events[i]) if err != nil { t.Fatalf("failed to add event: %s", err) } } if !util.Equal(clone.Root(), tree.Root()) { t.Fatal("expected equal roots") } }
// Verify verifies a membership proof func (p *MembershipProof) Verify() (correct bool) { if p.Root == nil || p.Event == nil || p.Index < 0 || p.Version < 0 { return false } proofTree := NewTree() for _, n := range p.Nodes { proofTree.frozen = proofTree.frozen.Set(n.Position.toString(), n.Hash) } proofTree.events = proofTree.events.Set(strconv.Itoa(p.Index), p.Event) c, err := proofTree.getHashedNode(0, proofTree.calculateDepth(p.Version+1), p.Version, true) if err != nil { return false } return util.Equal(c, p.Root) }
func TestBalloon(t *testing.T) { /* Basics */ sk, vk, err := Genkey() if err != nil { t.Fatalf("failed to generate keys: %s", err) } // setup for an initially empty Balloon for the author author, s0, err := Setup(nil, sk, vk, NewTestEventStorage()) if err != nil { t.Fatalf("failed to setup balloon: %s", err) } // update the Balloon with some events size := 10 events := make([]Event, size) for i := 0; i < size; i++ { k := make([]byte, util.HashOutputLen) _, err = rand.Read(k) if err != nil { t.Fatalf("failed to read random bytes: %s", err) } events[i].Key = k events[i].Value = util.Hash(k) } s1, err := author.Update(events, s0, sk) if err != nil { t.Fatalf("failed to update to s1: %s", err) } // for the server, create an empty balloon server := NewBalloon(NewTestEventStorage()) // refresh with the initial empty events err = server.Refresh(nil, nil, s0, vk) if err != nil { t.Fatalf("failed to do initial refresh: %s", err) } // refresh with events err = server.Refresh(events, s0, s1, vk) if err != nil { t.Fatalf("failed to do refresh from s0 to s1: %s", err) } // create some more events, update, and refresh for i := 0; i < size; i++ { k := make([]byte, util.HashOutputLen) _, err = rand.Read(k) if err != nil { t.Fatalf("failed to read random bytes: %s", err) } events[i].Key = k events[i].Value = util.Hash(k) } s2, err := author.Update(events[:1], s1, sk) if err != nil { t.Fatalf("failed to update to 2: %s", err) } // refresh with events err = server.Refresh(events[:1], s1, s2, vk) if err != nil { t.Fatalf("failed to do refresh from s1 to s2: %s", err) } /* Membership queries */ // check if the event we just inserted into the Balloon exists in the latest snapshot answer, event, proof, err := server.QueryMembership(events[0].Key, s2, vk) if err != nil { t.Fatalf("failed to perform a membership query: %s", err) } if !answer { t.Fatal("got a false answer for a membership query that should have answered true") } if event == nil { t.Fatal("got an empty event for a membership query that should have returned an event") } if !proof.Verify(events[0].Key, s2, s2, answer, event, vk) { t.Fatal("failed to verify valid membership proof") } if proof.Size() == 0 { t.Fatal("expected a non-zero sized membership proof") } // query for same event in previous snapshot, where it should not exist answer, event, proof, err = server.QueryMembership(events[0].Key, s1, vk) if err != nil { t.Fatalf("failed to perform a membership query: %s", err) } if answer { t.Fatal("got a true answer for a membership query that should have answered false") } if event != nil { t.Fatal("got an event for a membership query that should have not returned an event") } if !proof.Verify(events[0].Key, s1, s2, answer, event, vk) { t.Fatal("failed to verify valid membership proof") } // query for a random key in latest snapshot key := make([]byte, util.HashOutputLen) _, err = rand.Read(key) if err != nil { t.Fatalf("failed to read random bytes: %s", err) } answer, event, proof, err = server.QueryMembership(key, s2, vk) if err != nil { t.Fatalf("failed to perform a membership query: %s", err) } if answer { t.Fatal("got a true answer for a membership query that should have answered false") } if event != nil { t.Fatal("got an event for a membership query that should have not returned an event") } if !proof.Verify(key, s2, s2, answer, event, vk) { t.Fatal("failed to verify valid membership proof") } // query for the random key in a prior snapshot answer, event, proof, err = server.QueryMembership(key, s1, vk) if err != nil { t.Fatalf("failed to perform a membership query: %s", err) } if answer { t.Fatal("got a true answer for a membership query that should have answered false") } if event != nil { t.Fatal("got an event for a membership query that should have not returned an event") } if !proof.Verify(key, s1, s2, answer, event, vk) { t.Fatal("failed to verify valid membership proof") } /* Pruned queries */ // start with a prune query for events already inserted answer, pproof := server.QueryPrune(events, vk, true) if !pproof.Verify(events, answer, s2, vk) { t.Fatal("failed to verify a valid query prune proof") } if answer { t.Fatal("got a true reply to a prune query with old events") } if pproof.Size() == 0 { t.Fatal("expected a non-zero sized membership proof") } // randomise events for i := 0; i < size; i++ { k := make([]byte, util.HashOutputLen) _, err = rand.Read(k) if err != nil { t.Fatalf("failed to read random bytes: %s", err) } events[i].Key = k events[i].Value = util.Hash(k) } // query again answer, pproof = server.QueryPrune(events, vk, true) if !pproof.Verify(events, answer, s2, vk) { t.Fatal("failed to verify a valid query prune proof") } if !answer { t.Fatal("got a false reply to a prune query with random events") } /* Update using pruned queries */ s3u, err := pproof.Update(events, s2, sk) if err != nil { t.Fatalf("failed to update using prune: %s", err) } s3, err := author.Update(events, s2, sk) if err != nil { t.Fatalf("failed to update to 3: %s", err) } if !util.Equal(s3.Roots.History, s3u.Roots.History) { t.Fatal("Update (prune) produced a different history tree root") } if !util.Equal(s3.Roots.Treap, s3u.Roots.Treap) { t.Fatal("Update (prune) produced a different hash treap root") } if s3.Roots.Version != s3u.Roots.Version { t.Fatal("Update (prune) produced a different version") } /* Use RefreshVerify at the server */ if !server.RefreshVerify(events, s2, s3, vk) { t.Fatal("failed to refresh verify at server from 2 to 3") } }
func TestBalloonDetails(t *testing.T) { // More specific tests for test coverage // setup for an initially empty Balloon for an author sk, vk, err := Genkey() if err != nil { t.Fatalf("failed to generate keys: %s", err) } author, s0, err := Setup(nil, sk, vk, NewTestEventStorage()) if err != nil { t.Fatalf("failed to setup balloon: %s", err) } // update the Balloon with some events size := 10 events := make([]Event, size) for i := 0; i < size; i++ { k := make([]byte, util.HashOutputLen) _, err = rand.Read(k) if err != nil { t.Fatalf("failed to read random bytes: %s", err) } events[i].Key = k events[i].Value = util.Hash(k) } snapUpdate, err := author.Update(events, s0, sk) if err != nil { t.Fatalf("failed to update to s1: %s", err) } // create a second balloon, but use Setup directly _, snapSetup, err := Setup(events, sk, vk, NewTestEventStorage()) if err != nil { t.Fatalf("failed to setup balloon: %s", err) } if snapUpdate.Roots.Version != snapSetup.Roots.Version || !util.Equal(snapUpdate.Roots.History, snapSetup.Roots.History) || !util.Equal(snapUpdate.Roots.Treap, snapSetup.Roots.Treap) { t.Fatal("snapshots using Update and Setup differ") } // Refresh server := NewBalloon(NewTestEventStorage()) err = server.Refresh(nil, nil, s0, vk) if err != nil { t.Fatalf("failed to do initial refresh: %s", err) } err = server.Refresh(events, nil, snapUpdate, vk) if err == nil { t.Fatalf("did Refresh with wrong current snapshot") } err = server.Refresh(events, s0, snapUpdate, vk) if err != nil { t.Fatalf("failed to refresh from s0 to snapSetup: %s", err) } err = server.Refresh(events, nil, snapUpdate, vk) if err == nil { t.Fatalf("did Refresh with wrong current snapshot") } err = server.Refresh(events, snapUpdate, snapUpdate, vk) if err == nil { t.Fatalf("did Refresh with wrong current snapshot") } err = server.Refresh(events, s0, snapUpdate, vk) if err == nil { t.Fatalf("did Refresh with wrong current snapshot") } // Setup events = make([]Event, size) _, _, err = Setup(events, sk, vk, NewTestEventStorage()) if err == nil { t.Fatal("successfully Setup with nil event keys and values") } // Update _, err = author.Update(events, snapUpdate, sk) if err == nil { t.Fatal("successfully Update with nil event keys and values") } _, err = author.Update(events, s0, sk) if err == nil { t.Fatal("successfully Update with old snapshot") } events = make([]Event, 0, size) _, err = author.Update(events, snapUpdate, sk) if err == nil { t.Fatal("successfully Update with no events") } // QueryMembership _, _, _, err = author.QueryMembership([]byte("too short key"), s0, vk) if err == nil { t.Fatalf("membership query for too short key: %s", err) } // flip every byte in the signature of the snapshot for i := range s0.Signature { s0.Signature[i] ^= 0x40 _, _, _, err = author.QueryMembership(util.Hash([]byte("a valid key")), s0, vk) if err == nil { t.Fatalf("membership query for invalid signature: %s", err) } s0.Signature[i] ^= 0x40 } }
// Refresh updates balloon with the slice of events. Returns an error if the update // fails to produce a snapshot identical to the provided next one. // Run by the server on input (events and next) that will be verified. func (balloon *Balloon) Refresh(events []Event, current, next *Snapshot, vk []byte) (err error) { // current can be empty on the first run of Refresh if current == nil && balloon.Size() > 0 { return errors.New("current snapshot required for non-zero Balloon") } if current != nil { if !util.Equal(current.Roots.History, balloon.history.Root()) || !util.Equal(current.Roots.Treap, balloon.treap.Root()) { return errors.New("provided snapshot is not current") } if current.Roots.Version == -1 && next.Roots.Version != len(events)-1 { return errors.New("unexpected version in next snapshot") } if current.Roots.Version != -1 && current.Roots.Version+len(events) != next.Roots.Version { return errors.New("version in next snapshot inconsistent with current") } if !util.Verify(vk, append(append([]byte("snapshot"), current.Roots.History...), append(current.Roots.Treap, current.Previous...)...), current.Signature) { return errors.New("invalid signature in current snapshot") } } treap := balloon.treap ht := balloon.history.Clone() if len(events) > 0 { sort.Sort(ByKey(events)) // attempt to add events for i := 0; i < len(events); i++ { // add the hash of the entire event to the history tree _, err = ht.Add(util.Hash(append(events[i].Key, events[i].Value...))) if err != nil { return } // add to the treap the hash of the key pointing to the index (version) of the // hash of the event in the history tree treap, err = treap.Add(util.Hash(events[i].Key), util.Itob(ht.LatestVersion())) if err != nil { return } } } var prev []byte if current != nil { prev = current.Signature } // compare snapshot with next snapshot if !util.Equal(ht.Root(), next.Roots.History) || !util.Equal(treap.Root(), next.Roots.Treap) || ht.LatestVersion() != next.Roots.Version || !util.Equal(prev, next.Previous) { return errors.New("roots or version mismatch") } if current == nil { if next.Index != 0 { return errors.New("index not what expected in next snapshot") } } else if current.Index+1 != next.Index { return errors.New("index not what expected in next snapshot") } if !util.Verify(vk, append(append([]byte("snapshot"), next.Roots.History...), append(next.Roots.Treap, next.Previous...)...), next.Signature) { return errors.New("invalid signature") } // all is OK, store results err = balloon.Storage.Store(events, *next) if err != nil { return err } balloon.latestsnapshot = *next if len(events) > 0 { balloon.latesteventkey = events[len(events)-1].Key } balloon.treap = treap balloon.history = ht return }