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

create proofs with post state #384

Merged
merged 4 commits into from
Sep 12, 2023
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
205 changes: 157 additions & 48 deletions proof_ipa.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,14 @@ type Proof struct {
Cs []*Point // commitments, sorted by their path in the tree
PoaStems [][]byte // stems proving another stem is absent
Keys [][]byte
Values [][]byte
PreValues [][]byte
PostValues [][]byte
}

type SuffixStateDiff struct {
Suffix byte `json:"suffix"`
CurrentValue *[32]byte `json:"currentValue"`
NewValue *[32]byte `json:"newValue"`
}

type SuffixStateDiffs []SuffixStateDiff
Expand All @@ -80,38 +82,78 @@ func GetCommitmentsForMultiproof(root VerkleNode, keys [][]byte, resolver NodeRe
return root.GetProofItems(keylist(keys), resolver)
}

func MakeVerkleMultiProof(root VerkleNode, keys [][]byte, resolver NodeResolverFn) (*Proof, []*Point, []byte, []*Fr, error) {
// getProofElementsFromTree factors the logic that is used both in the proving and verification methods. It takes a pre-state
// tree and an optional post-state tree, extracts the proof data from them and returns all the items required to build/verify
// a proof.
func getProofElementsFromTree(preroot, postroot VerkleNode, keys [][]byte, resolver NodeResolverFn) (*ProofElements, []byte, [][]byte, [][]byte, []*Point, [][]Fr, []*Fr, []byte, error) {
jsign marked this conversation as resolved.
Show resolved Hide resolved
// go-ipa won't accept no key as an input, catch this corner case
// and return an empty result.
if len(keys) == 0 {
return nil, nil, nil, nil, errors.New("no key provided for proof")
return nil, nil, nil, nil, nil, nil, nil, nil, errors.New("no key provided for proof")
}

tr := common.NewTranscript("vt")
root.Commit()
pe, es, poas, err := GetCommitmentsForMultiproof(preroot, keys, resolver)
if err != nil {
return nil, nil, nil, nil, nil, nil, nil, nil, fmt.Errorf("error getting pre-state proof data: %w", err)
}

// List of points and vectors and indices used to generate the IPA proof
// If only a pre-state root is available, it's a simple copy, but if both
// pre- and post-state root are present, it's merging the two lists.
var (
proof_cis []*Point
proof_fs [][]Fr
proof_zs []uint8
proof_ys []*Fr
postvals = make([][]byte, len(keys))
)
for i := range pe.Cis {
proof_cis = append(proof_cis, pe.Cis[i])
proof_fs = append(proof_fs, pe.Fis[i])
proof_ys = append(proof_ys, pe.Yis[i])
proof_zs = append(proof_zs, pe.Zis[i])
}

pe, es, poas, err := GetCommitmentsForMultiproof(root, keys, resolver)
// if a post-state tree is present, merge its proof elements with
// those of the pre-state tree, so that they can be proved together.
if postroot != nil {
jsign marked this conversation as resolved.
Show resolved Hide resolved
pe_post, _, _, err := GetCommitmentsForMultiproof(postroot, keys, resolver)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One question: here keys is the same set of keys used for pre-state and post-state proving.

But if the block execution read 1000 keys but only wrote into 1 key; shouldn't keys in this line only have 1 key instead of the 1000 ones? If that isn't the case, that would probably mean that proof_(c/f/y/z)s will have duplicate cuadruples.

As in, in the post-state VKT, there will be keys in keys that have the same value as in the pre-state VKT (e.g: 999 in this example). So, that means that there will be many repeated openings.

Maybe I'm a bit confused.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well the opening themselves will not be integrated to the proof. But you are correct that they will be computed. This is reusing a function that does too much, and at the end values that are just read will find themselves in the proof. This is not incorrect but it's much more than necessary.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not going to fix it in this PR, created #393 to track it.

if err != nil {
return nil, nil, nil, nil, nil, nil, nil, nil, fmt.Errorf("error getting post-state proof data: %w", err)
}

for i := range pe_post.Cis {
proof_cis = append(proof_cis, pe_post.Cis[i])
proof_fs = append(proof_fs, pe_post.Fis[i])
proof_ys = append(proof_ys, pe_post.Yis[i])
proof_zs = append(proof_zs, pe_post.Zis[i])
}

// Set the post values, if they are untouched, leave them `nil`
for i, v := range pe.Vals {
if !bytes.Equal(v, pe_post.Vals[i]) {
postvals[i] = pe_post.Vals[i]
}
}
}

// [0:3]: proof elements of the pre-state trie for serialization,
// 3: values to be inserted in the post-state trie for serialization
// [4:8]: aggregated pre+post elements for proving
return pe, es, poas, postvals, proof_cis, proof_fs, proof_ys, proof_zs, nil
}

func MakeVerkleMultiProof(preroot, postroot VerkleNode, keys [][]byte, resolver NodeResolverFn) (*Proof, []*Point, []byte, []*Fr, error) {
pe, es, poas, postvals, proof_cis, proof_fs, _, proof_zs, err := getProofElementsFromTree(preroot, postroot, keys, resolver)
if err != nil {
return nil, nil, nil, nil, fmt.Errorf("get commitments for multiproof: %s", err)
}

// NOTE this is leftover code from the time the proof was
// made against the POST state. Since proofs are expected
// to prove PRE and POST state in the future, I'm leaving
// this for reference - eventhough it's unlikely that the
// final version will look like this, but you never know.
// var vals [][]byte
// for _, k := range keys {
// // TODO at the moment, do not include the post-data
// // val, _ := root.Get(k, nil)
// // vals = append(vals, val)
// vals = append(vals, keyvals[string(k)])
// }

cfg := GetConfig()
mpArg, err := ipa.CreateMultiProof(tr, cfg.conf, pe.Cis, pe.Fis, pe.Zis)
tr := common.NewTranscript("vt")
mpArg, err := ipa.CreateMultiProof(tr, cfg.conf, proof_cis, proof_fs, proof_zs)
if err != nil {
return nil, nil, nil, nil, fmt.Errorf("creating multiproof: %s", err)
return nil, nil, nil, nil, fmt.Errorf("creating multiproof: %w", err)
}

// It's wheel-reinvention time again 🎉: reimplement a basic
Expand All @@ -130,17 +172,34 @@ func MakeVerkleMultiProof(root VerkleNode, keys [][]byte, resolver NodeResolverF
for i, path := range paths {
cis[i] = pe.ByPath[path]
}

proof := &Proof{
Multipoint: mpArg,
Cs: cis,
ExtStatus: es,
PoaStems: poas,
Keys: keys,
Values: pe.Vals,
PreValues: pe.Vals,
PostValues: postvals,
}
return proof, pe.Cis, pe.Zis, pe.Yis, nil
}

// VerifyVerkleProofWithPreAndPostTrie takes a pre-state trie, a post-state trie and a list of keys, and verifies that
// the provided proof verifies. If the post-state trie is `nil`, the behavior is the same as `VerifyVerkleProof`.
func VerifyVerkleProofWithPreAndPostTrie(proof *Proof, preroot, postroot VerkleNode) error {
_, _, _, _, proof_cis, _, proof_ys, proof_zs, err := getProofElementsFromTree(preroot, postroot, proof.Keys, nil)
if err != nil {
return fmt.Errorf("error getting proof elements: %w", err)
}

if ok, err := VerifyVerkleProof(proof, proof_cis, proof_zs, proof_ys, GetConfig()); !ok || err != nil {
return fmt.Errorf("error verifying proof: verifies=%v, error=%w", ok, err)
}

return nil
}

func VerifyVerkleProof(proof *Proof, Cs []*Point, indices []uint8, ys []*Fr, tc *Config) (bool, error) {
tr := common.NewTranscript("vt")
return ipa.CheckMultiProof(tr, tc.conf, proof.Multipoint, Cs, ys, indices)
Expand Down Expand Up @@ -181,26 +240,35 @@ func SerializeProof(proof *Proof) (*VerkleProof, StateDiff, error) {
stemdiff = &statediff[len(statediff)-1]
copy(stemdiff.Stem[:], key[:31])
}
var valueLen = len(proof.Values[i])
stemdiff.SuffixDiffs = append(stemdiff.SuffixDiffs, SuffixStateDiff{Suffix: key[31]})
newsd := &stemdiff.SuffixDiffs[len(stemdiff.SuffixDiffs)-1]

var valueLen = len(proof.PreValues[i])
switch valueLen {
case 0:
// null value
jsign marked this conversation as resolved.
Show resolved Hide resolved
case 32:
newsd.CurrentValue = (*[32]byte)(proof.PreValues[i])
default:
var aligned [32]byte
copy(aligned[:valueLen], proof.PreValues[i])
newsd.CurrentValue = (*[32]byte)(unsafe.Pointer(&aligned[0]))
}

valueLen = len(proof.PostValues[i])
switch valueLen {
case 0:
stemdiff.SuffixDiffs = append(stemdiff.SuffixDiffs, SuffixStateDiff{
Suffix: key[31],
})
// null value
case 32:
stemdiff.SuffixDiffs = append(stemdiff.SuffixDiffs, SuffixStateDiff{
Suffix: key[31],
CurrentValue: (*[32]byte)(proof.Values[i]),
})
newsd.NewValue = (*[32]byte)(proof.PostValues[i])
default:
// TODO remove usage of unsafe
var aligned [32]byte
copy(aligned[:valueLen], proof.Values[i])
stemdiff.SuffixDiffs = append(stemdiff.SuffixDiffs, SuffixStateDiff{
Suffix: key[31],
CurrentValue: (*[32]byte)(unsafe.Pointer(&aligned[0])),
})
copy(aligned[:valueLen], proof.PostValues[i])
newsd.NewValue = (*[32]byte)(unsafe.Pointer(&aligned[0]))
jsign marked this conversation as resolved.
Show resolved Hide resolved
}
}

return &VerkleProof{
OtherStems: otherstems,
DepthExtensionPresent: proof.ExtStatus,
Expand All @@ -218,10 +286,11 @@ func SerializeProof(proof *Proof) (*VerkleProof, StateDiff, error) {
// can be used to rebuild a stateless version of the tree.
func DeserializeProof(vp *VerkleProof, statediff StateDiff) (*Proof, error) {
var (
poaStems, keys, values [][]byte
extStatus []byte
commitments []*Point
multipoint ipa.MultiProof
poaStems, keys [][]byte
prevalues, postvalues [][]byte
extStatus []byte
commitments []*Point
multipoint ipa.MultiProof
)

poaStems = make([][]byte, len(vp.OtherStems))
Expand Down Expand Up @@ -259,9 +328,15 @@ func DeserializeProof(vp *VerkleProof, statediff StateDiff) (*Proof, error) {
k[31] = suffixdiff.Suffix
keys = append(keys, k[:])
if suffixdiff.CurrentValue != nil {
values = append(values, suffixdiff.CurrentValue[:])
prevalues = append(prevalues, suffixdiff.CurrentValue[:])
} else {
values = append(values, nil)
prevalues = append(prevalues, nil)
}

if suffixdiff.NewValue != nil {
postvalues = append(postvalues, suffixdiff.NewValue[:])
} else {
postvalues = append(postvalues, nil)
}
}
}
Expand All @@ -272,7 +347,8 @@ func DeserializeProof(vp *VerkleProof, statediff StateDiff) (*Proof, error) {
commitments,
poaStems,
keys,
values,
prevalues,
postvalues,
}
return &proof, nil
}
Expand All @@ -285,8 +361,8 @@ type stemInfo struct {
stem []byte
}

// TreeFromProof builds a stateless tree from the proof
func TreeFromProof(proof *Proof, rootC *Point) (VerkleNode, error) { // skipcq: GO-R1005
// PreStateTreeFromProof builds a stateless prestate tree from the proof.
func PreStateTreeFromProof(proof *Proof, rootC *Point) (VerkleNode, error) { // skipcq: GO-R1005
stems := make([][]byte, 0, len(proof.Keys))
for _, k := range proof.Keys {
if len(stems) == 0 || !bytes.Equal(stems[len(stems)-1], k[:31]) {
Expand Down Expand Up @@ -322,8 +398,8 @@ func TreeFromProof(proof *Proof, rootC *Point) (VerkleNode, error) { // skipcq:
stemPath := stems[stemIndex][:len(path)]
si.values = map[byte][]byte{}
for i, k := range proof.Keys {
if bytes.Equal(k[:len(path)], stemPath) && proof.Values[i] != nil {
si.values[k[31]] = proof.Values[i]
if bytes.Equal(k[:len(path)], stemPath) && proof.PreValues[i] != nil {
si.values[k[31]] = proof.PreValues[i]
si.has_c1 = si.has_c1 || (k[31] < 128)
si.has_c2 = si.has_c2 || (k[31] >= 128)
// This key has values, its stem is the one that
Expand Down Expand Up @@ -356,14 +432,14 @@ func TreeFromProof(proof *Proof, rootC *Point) (VerkleNode, error) { // skipcq:
// but not for block validation.
values := make([][]byte, NodeWidth)
for i, k := range proof.Keys {
if len(proof.Values[i]) == 0 {
if len(proof.PreValues[i]) == 0 {
// Skip the nil keys, they are here to prove
// an absence.
continue
}

if bytes.Equal(k[:31], info[string(p)].stem) {
values[k[31]] = proof.Values[i]
values[k[31]] = proof.PreValues[i]
}
Comment on lines 441 to 443
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still feel this if is a bit strange. We're reconstructing the values to be assigned in the leaf when we'll soon create the LeafNode.

But see what we do in CreatePath(...):

Is the latter needed? Or a noop? Or maybe I'm missing something.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I can't remember why that is, and removing it seems to work. I won't remove it in this PR but I'm making a note of it in #395

}
comms, err = root.CreatePath(p, info[string(p)], comms, values)
Expand All @@ -374,3 +450,36 @@ func TreeFromProof(proof *Proof, rootC *Point) (VerkleNode, error) { // skipcq:

return root, nil
}

// PostStateTreeFromProof uses the pre-state trie and the list of updated values
// to produce the stateless post-state trie.
func PostStateTreeFromStateDiff(preroot VerkleNode, statediff StateDiff) (VerkleNode, error) {
postroot := preroot.Copy()

for _, stemstatediff := range statediff {
var (
values = make([][]byte, NodeWidth)
overwrites bool
)

for _, suffixdiff := range stemstatediff.SuffixDiffs {
if /* len(suffixdiff.NewValue) > 0 - this only works for a slice */ suffixdiff.NewValue != nil {
// if this value is non-nil, it means InsertStem should be
// called, otherwise, skip updating the tree.
overwrites = true
values[suffixdiff.Suffix] = suffixdiff.NewValue[:]
}
}

if overwrites {
var stem [31]byte
copy(stem[:31], stemstatediff.Stem[:])
if err := postroot.(*InternalNode).InsertStem(stem[:], values, nil); err != nil {
return nil, fmt.Errorf("error overwriting value in post state: %w", err)
}
}
}
postroot.Commit()

return postroot, nil
}
24 changes: 20 additions & 4 deletions proof_json.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ func (vp *VerkleProof) UnmarshalJSON(data []byte) error {
var aux verkleProofMarshaller
err := json.Unmarshal(data, &aux)
if err != nil {
return err
return fmt.Errorf("verkle proof unmarshal error: %w", err)
}

vp.DepthExtensionPresent, err = PrefixedHexStringToBytes(aux.DepthExtensionPresent)
Expand Down Expand Up @@ -198,7 +198,7 @@ func (ssd StemStateDiff) MarshalJSON() ([]byte, error) {
func (ssd *StemStateDiff) UnmarshalJSON(data []byte) error {
var aux stemStateDiffMarshaller
if err := json.Unmarshal(data, &aux); err != nil {
return err
return fmt.Errorf("stemdiff unmarshal error: %w", err)
}

stem, err := PrefixedHexStringToBytes(aux.Stem)
Expand All @@ -215,25 +215,31 @@ func (ssd *StemStateDiff) UnmarshalJSON(data []byte) error {
type suffixStateDiffMarshaller struct {
Suffix byte `json:"suffix"`
CurrentValue *string `json:"currentValue"`
NewValue *string `json:"newValue"`
}

func (ssd SuffixStateDiff) MarshalJSON() ([]byte, error) {
var cvstr *string
var cvstr, nvstr *string
if ssd.CurrentValue != nil {
tempstr := HexToPrefixedString(ssd.CurrentValue[:])
cvstr = &tempstr
}
if ssd.NewValue != nil {
tempstr := HexToPrefixedString(ssd.NewValue[:])
nvstr = &tempstr
}
return json.Marshal(&suffixStateDiffMarshaller{
Suffix: ssd.Suffix,
CurrentValue: cvstr,
NewValue: nvstr,
})
}

func (ssd *SuffixStateDiff) UnmarshalJSON(data []byte) error {
aux := &suffixStateDiffMarshaller{}

if err := json.Unmarshal(data, &aux); err != nil {
return err
return fmt.Errorf("suffix diff unmarshal error: %w", err)
}

if aux.CurrentValue != nil && len(*aux.CurrentValue) != 64 && len(*aux.CurrentValue) != 0 && len(*aux.CurrentValue) != 66 {
Expand All @@ -254,5 +260,15 @@ func (ssd *SuffixStateDiff) UnmarshalJSON(data []byte) error {
copy(ssd.CurrentValue[:], currentValueBytes)
}

if aux.NewValue != nil && len(*aux.NewValue) != 0 {
newValueBytes, err := PrefixedHexStringToBytes(*aux.NewValue)
if err != nil {
return fmt.Errorf("error decoding hex string for current value: %v", err)
}

ssd.NewValue = &[32]byte{}
copy(ssd.NewValue[:], newValueBytes)
}

return nil
}
Loading