mirror of
https://github.com/schollz/croc.git
synced 2025-10-11 13:21:00 +02:00
add pake
This commit is contained in:
parent
283bf704a2
commit
f6751dadb9
5 changed files with 332 additions and 104 deletions
|
@ -1,46 +0,0 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at zack.scholl@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
|
@ -1,8 +1,6 @@
|
|||
package croc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"net/url"
|
||||
"os"
|
||||
|
@ -19,11 +17,15 @@ func (c *Croc) client(role int) (err error) {
|
|||
|
||||
// initialize the channel data for this client
|
||||
c.cs.Lock()
|
||||
c.cs.channel = newChannelData("")
|
||||
c.cs.channel.codePhrase = codePhrase
|
||||
c.cs.channel.Channel = codePhrase[:3]
|
||||
channel := codePhrase[:3]
|
||||
c.cs.channel.secret["pw"] = []byte(codePhrase[3:])
|
||||
if len(codePhrase) > 0 {
|
||||
if len(codePhrase) < 4 {
|
||||
err = errors.New("code phrase must be more than 4 characters")
|
||||
return
|
||||
}
|
||||
c.cs.channel.Channel = codePhrase[:3]
|
||||
c.cs.channel.passPhrase = codePhrase[3:]
|
||||
}
|
||||
c.cs.Unlock()
|
||||
|
||||
interrupt := make(chan os.Signal, 1)
|
||||
|
@ -136,30 +138,12 @@ func (c *Croc) processState(cd channelData) (err error) {
|
|||
c.cs.channel.TransferReady = true
|
||||
}
|
||||
c.cs.channel.Ports = cd.Ports
|
||||
for key := range cd.State {
|
||||
c.cs.channel.State[key] = cd.State[key]
|
||||
}
|
||||
// update the curve
|
||||
_, c.cs.channel.curve = getCurve(string(c.cs.channel.State["curve"]))
|
||||
|
||||
// TODO:
|
||||
// process the client state
|
||||
log.Debugf("processing client state: %+v", c.cs.channel.String2())
|
||||
if c.cs.channel.Role == 0 {
|
||||
// processing for sender
|
||||
|
||||
// *Does X not exist?*
|
||||
// - Generates X from pw.
|
||||
// - Update relay with X.
|
||||
if bytes.Equal(c.cs.channel.State["Xᵤ"], []byte{}) {
|
||||
random1 := make([]byte, 8)
|
||||
rand.Read(random1)
|
||||
random2 := make([]byte, 8)
|
||||
rand.Read(random2)
|
||||
c.cs.channel.State["Uᵤ"], c.cs.channel.State["Uᵥ"] = []byte(c.cs.channel.curve.ScalarBaseMult(random1))
|
||||
c.cs.channel.State["Vᵤ"], c.cs.channel.State["Vᵥ"] = []byte(c.cs.channel.curve.ScalarBaseMult(random2))
|
||||
}
|
||||
|
||||
} else if c.cs.channel.Role == 1 {
|
||||
// processing for recipient
|
||||
}
|
||||
|
|
|
@ -2,13 +2,13 @@ package croc
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/elliptic"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/schollz/croc/src/pake"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -16,17 +16,6 @@ const (
|
|||
bufferSize = 1024
|
||||
)
|
||||
|
||||
var (
|
||||
// TODO:
|
||||
// MAKE EVERYTHING HERE PART OF THE CHANNELDATA!
|
||||
|
||||
// see PAKE setup for more info: https://play.golang.org/p/Sd0eTuuEIWu
|
||||
// availableStates are the varaibles available to the parties involved
|
||||
availableStates = []string{"curve", "Xᵤ", "Xᵥ", "Yᵤ", "Yᵥ", "Uᵤ", "Uᵥ", "Vᵤ", "Vᵥ", "Bcrypt(Ak)", "Bcrypt(Bk)"}
|
||||
// availableSecrets are the variables available only to a specific client, and not shared
|
||||
availableSecrets = []string{"pw", "Upwᵤ", "Upwᵥ", "α", "αᵤ", "αᵥ", "Vpwᵤ", "Vpwᵥ", "β", "gβᵤ", "gβᵥ", "BZᵤ", "BZᵥ", "BZᵤ", "BZᵥ", "AZᵤ", "AZᵥ", "AZᵤ", "AZᵥ", "Bk", "Ak"}
|
||||
)
|
||||
|
||||
type Croc struct {
|
||||
TcpPorts []string
|
||||
ServerPort string
|
||||
|
@ -74,9 +63,9 @@ type channelData struct {
|
|||
// Public
|
||||
// Channel is the name of the channel
|
||||
Channel string `json:"channel,omitempty"`
|
||||
// State contains state variables that are public to both parties
|
||||
// contains "curve", "h_k", "hh_k", "x", "y"
|
||||
State map[string][]byte `json:"state"`
|
||||
// Pake contains the information for
|
||||
// generating the session key over an insecure channel
|
||||
Pake pake.Pake
|
||||
// TransferReady is set by the relaying when both parties have connected
|
||||
// with their credentials
|
||||
TransferReady bool `json:"transfer_ready"`
|
||||
|
@ -97,11 +86,10 @@ type channelData struct {
|
|||
// codePhrase uses the first 3 characters to establish a channel, and the rest
|
||||
// to form the passphrase
|
||||
codePhrase string
|
||||
// secret are the computed secretes
|
||||
// contains "curve", "h_k", "hh_k", "x", "y"
|
||||
secret map[string][]byte
|
||||
// curve is the type of elliptic curve used for PAKE
|
||||
curve elliptic.Curve
|
||||
// passPhrase is used to generate a session key
|
||||
passPhrase string
|
||||
// sessionKey
|
||||
sessionKey []byte
|
||||
|
||||
// relay parameters
|
||||
// isopen determine whether or not the channel has been opened
|
||||
|
@ -152,17 +140,3 @@ type payload struct {
|
|||
// Close set to true when closing:
|
||||
Close bool `json:"close"`
|
||||
}
|
||||
|
||||
func newChannelData(name string) (cd *channelData) {
|
||||
cd = new(channelData)
|
||||
cd.Channel = name
|
||||
cd.State = make(map[string][]byte)
|
||||
for _, s := range availableStates {
|
||||
cd.State[s] = []byte{}
|
||||
}
|
||||
cd.secret = make(map[string][]byte)
|
||||
for _, s := range availableSecrets {
|
||||
cd.secret[s] = []byte{}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
261
src/pake/pake.go
Normal file
261
src/pake/pake.go
Normal file
|
@ -0,0 +1,261 @@
|
|||
package pake
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// Pake keeps public and private variables by
|
||||
// only transmitting between parties after marshaling
|
||||
//
|
||||
// This method follows
|
||||
// https://crypto.stanford.edu/~dabo/cryptobook/BonehShoup_0_4.pdf
|
||||
// Figure 21/15
|
||||
// http://www.lothar.com/~warner/MagicWormhole-PyCon2016.pdf
|
||||
// Slide 11
|
||||
|
||||
type Pake struct {
|
||||
// Public variables
|
||||
Role int
|
||||
Uᵤ, Uᵥ *big.Int
|
||||
Vᵤ, Vᵥ *big.Int
|
||||
Xᵤ, Xᵥ *big.Int
|
||||
Yᵤ, Yᵥ *big.Int
|
||||
HkA, HkB []byte
|
||||
|
||||
// Private variables
|
||||
curve elliptic.Curve
|
||||
pw []byte
|
||||
vpwᵤ, vpwᵥ *big.Int
|
||||
upwᵤ, upwᵥ *big.Int
|
||||
α []byte
|
||||
αᵤ, αᵥ *big.Int
|
||||
zᵤ, zᵥ *big.Int
|
||||
k []byte
|
||||
|
||||
isVerified bool
|
||||
}
|
||||
|
||||
func Init(pw []byte, role int, curve elliptic.Curve) (p *Pake, err error) {
|
||||
p = new(Pake)
|
||||
if role == 1 {
|
||||
p.Role = 1
|
||||
p.curve = curve
|
||||
p.pw = pw
|
||||
} else {
|
||||
p.Role = 0
|
||||
p.curve = curve
|
||||
p.pw = pw
|
||||
rand1 := make([]byte, 8)
|
||||
rand2 := make([]byte, 8)
|
||||
rand.Read(rand1)
|
||||
rand.Read(rand2)
|
||||
p.Uᵤ, p.Uᵥ = p.curve.ScalarBaseMult(rand1)
|
||||
p.Vᵤ, p.Vᵥ = p.curve.ScalarBaseMult(rand2)
|
||||
if !p.curve.IsOnCurve(p.Uᵤ, p.Uᵥ) {
|
||||
err = errors.New("U values not on curve")
|
||||
return
|
||||
}
|
||||
if !p.curve.IsOnCurve(p.Vᵤ, p.Vᵥ) {
|
||||
err = errors.New("V values not on curve")
|
||||
return
|
||||
}
|
||||
|
||||
// STEP: A computes X
|
||||
p.vpwᵤ, p.vpwᵥ = p.curve.ScalarMult(p.Vᵤ, p.Vᵥ, p.pw)
|
||||
p.upwᵤ, p.upwᵥ = p.curve.ScalarMult(p.Uᵤ, p.Uᵥ, p.pw)
|
||||
p.α = make([]byte, 8) // randomly generated secret
|
||||
rand.Read(p.α)
|
||||
p.αᵤ, p.αᵥ = p.curve.ScalarBaseMult(p.α)
|
||||
p.Xᵤ, p.Xᵥ = p.curve.Add(p.upwᵤ, p.upwᵥ, p.αᵤ, p.αᵥ) // "X"
|
||||
// now X should be sent to B
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Pake) Bytes() []byte {
|
||||
b, _ := json.Marshal(p)
|
||||
return b
|
||||
}
|
||||
|
||||
// Update will update itself
|
||||
func (p *Pake) Update(qBytes []byte) (err error) {
|
||||
var q *Pake
|
||||
err = json.Unmarshal(qBytes, &q)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if p.Role == q.Role {
|
||||
err = errors.New("can't have its own role")
|
||||
return
|
||||
}
|
||||
|
||||
if p.Role == 1 {
|
||||
// initial step for B
|
||||
if p.Uᵤ == nil && q.Uᵤ != nil {
|
||||
// copy over public variables
|
||||
p.Uᵤ, p.Uᵥ = q.Uᵤ, q.Uᵥ
|
||||
p.Vᵤ, p.Vᵥ = q.Vᵤ, q.Vᵥ
|
||||
p.Xᵤ, p.Xᵥ = q.Xᵤ, q.Xᵥ
|
||||
|
||||
// confirm that U,V are on curve
|
||||
if !p.curve.IsOnCurve(p.Uᵤ, p.Uᵥ) {
|
||||
err = errors.New("U values not on curve")
|
||||
return
|
||||
}
|
||||
if !p.curve.IsOnCurve(p.Vᵤ, p.Vᵥ) {
|
||||
err = errors.New("V values not on curve")
|
||||
return
|
||||
}
|
||||
|
||||
// STEP: B computes Y
|
||||
p.vpwᵤ, p.vpwᵥ = p.curve.ScalarMult(p.Vᵤ, p.Vᵥ, p.pw)
|
||||
p.upwᵤ, p.upwᵥ = p.curve.ScalarMult(p.Uᵤ, p.Uᵥ, p.pw)
|
||||
p.α = make([]byte, 8) // randomly generated secret
|
||||
rand.Read(p.α)
|
||||
p.αᵤ, p.αᵥ = p.curve.ScalarBaseMult(p.α)
|
||||
p.Yᵤ, p.Yᵥ = p.curve.Add(p.vpwᵤ, p.vpwᵥ, p.αᵤ, p.αᵥ) // "Y"
|
||||
// STEP: B computes Z
|
||||
p.zᵤ, p.zᵥ = p.curve.Add(p.Xᵤ, p.Xᵥ, p.upwᵤ, new(big.Int).Neg(p.upwᵥ))
|
||||
p.zᵤ, p.zᵥ = p.curve.ScalarMult(p.zᵤ, p.zᵥ, p.α)
|
||||
// STEP: B computes k
|
||||
// H(pw,id_P,id_Q,X,Y,Z)
|
||||
HB := sha256.New()
|
||||
HB.Write(p.pw)
|
||||
HB.Write(p.Xᵤ.Bytes())
|
||||
HB.Write(p.Xᵥ.Bytes())
|
||||
HB.Write(p.Yᵤ.Bytes())
|
||||
HB.Write(p.Yᵥ.Bytes())
|
||||
HB.Write(p.zᵤ.Bytes())
|
||||
HB.Write(p.zᵥ.Bytes())
|
||||
// STEP: B computes k
|
||||
p.k = HB.Sum(nil)
|
||||
p.HkB, err = hashK(p.k)
|
||||
} else if p.HkA == nil && q.HkA != nil {
|
||||
p.HkA = q.HkA
|
||||
// verify
|
||||
err = checkKHash(p.HkA, p.k)
|
||||
if err == nil {
|
||||
p.isVerified = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if p.HkB == nil && q.HkB != nil {
|
||||
p.HkB = q.HkB
|
||||
p.Yᵤ, p.Yᵥ = q.Yᵤ, q.Yᵥ
|
||||
|
||||
// STEP: A computes Z
|
||||
p.zᵤ, p.zᵥ = p.curve.Add(p.Yᵤ, p.Yᵥ, p.vpwᵤ, new(big.Int).Neg(p.vpwᵥ))
|
||||
p.zᵤ, p.zᵥ = p.curve.ScalarMult(p.zᵤ, p.zᵥ, p.α)
|
||||
// STEP: A computes k
|
||||
// H(pw,id_P,id_Q,X,Y,Z)
|
||||
HA := sha256.New()
|
||||
HA.Write(p.pw)
|
||||
HA.Write(p.Xᵤ.Bytes())
|
||||
HA.Write(p.Xᵥ.Bytes())
|
||||
HA.Write(p.Yᵤ.Bytes())
|
||||
HA.Write(p.Yᵥ.Bytes())
|
||||
HA.Write(p.zᵤ.Bytes())
|
||||
HA.Write(p.zᵥ.Bytes())
|
||||
p.k = HA.Sum(nil)
|
||||
p.HkA, err = hashK(p.k)
|
||||
|
||||
// STEP: A verifies that its session key matches B's
|
||||
// session key
|
||||
err = checkKHash(p.HkB, p.k)
|
||||
if err == nil {
|
||||
p.isVerified = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// hashK generates a bcrypt hash of the password using work factor 14.
|
||||
func hashK(k []byte) ([]byte, error) {
|
||||
return bcrypt.GenerateFromPassword(k, 14)
|
||||
}
|
||||
|
||||
// checkKHash securely compares a bcrypt hashed password with its possible
|
||||
// plaintext equivalent. Returns nil on success, or an error on failure.
|
||||
func checkKHash(hash, k []byte) error {
|
||||
return bcrypt.CompareHashAndPassword(hash, k)
|
||||
}
|
||||
|
||||
// IsVerified returns whether or not the k has been
|
||||
// generated AND it confirmed to be the same as partner
|
||||
func (p *Pake) IsVerified() bool {
|
||||
return p.isVerified
|
||||
}
|
||||
|
||||
// SessionKey is returned, unless it is not generated
|
||||
// in which is returns an error. This function does
|
||||
// not check if it is verifies.
|
||||
func (p *Pake) SessionKey() ([]byte, error) {
|
||||
var err error
|
||||
if p.k == nil {
|
||||
err = errors.New("session key not generated")
|
||||
}
|
||||
return p.k, err
|
||||
}
|
||||
|
||||
// func main() {
|
||||
// // PUBLIC PARAMETERS (computed once)
|
||||
// p256 := elliptic.P256()
|
||||
// Uᵤ, Uᵥ := p256.ScalarBaseMult([]byte{1, 2, 3, 4})
|
||||
// Vᵤ, Vᵥ := p256.ScalarBaseMult([]byte{1, 2, 3, 4})
|
||||
// // PRIVATE PARAMATERS
|
||||
// // pw
|
||||
// pw := []byte{1, 1} // shared weak secret
|
||||
// // PROTOCOL
|
||||
// // STEP: A computes X
|
||||
// upwᵤ, upwᵥ := p256.ScalarMult(Uᵤ, Uᵥ, pw)
|
||||
// α := []byte{1, 2, 3, 4} // randomly generated secret
|
||||
// αᵤ, αᵥ := p256.ScalarBaseMult(α)
|
||||
// Xᵤ, Xᵥ := p256.Add(upwᵤ, upwᵥ, αᵤ, αᵥ) // "X"
|
||||
// // STEP: A sends X
|
||||
// // STEP: B computes Y
|
||||
// vpwᵤ, vpwᵥ := p256.ScalarMult(Vᵤ, Vᵥ, pw)
|
||||
// β := []byte{1, 2, 3, 4} // randomly generated secret
|
||||
// gβᵤ, gβᵥ := p256.ScalarBaseMult(β)
|
||||
// Yᵤ, Yᵥ := p256.Add(vpwᵤ, vpwᵥ, gβᵤ, gβᵥ) // "Y"
|
||||
// // STEP: B computes Z
|
||||
// BZᵤ, BZᵥ := p256.Add(Xᵤ, Xᵥ, upwᵤ, new(big.Int).Neg(upwᵥ))
|
||||
// BZᵤ, BZᵥ = p256.ScalarMult(BZᵤ, BZᵥ, β)
|
||||
// // STEP: B computes k
|
||||
// // H(pw,id_P,id_Q,X,Y,Z)
|
||||
// HB := sha256.New()
|
||||
// HB.Write(pw)
|
||||
// HB.Write(Xᵤ.Bytes())
|
||||
// HB.Write(Xᵥ.Bytes())
|
||||
// HB.Write(Yᵤ.Bytes())
|
||||
// HB.Write(Yᵥ.Bytes())
|
||||
// HB.Write(BZᵤ.Bytes())
|
||||
// HB.Write(BZᵥ.Bytes())
|
||||
// Bk := HB.Sum(nil)
|
||||
// // STEP: B sends Y
|
||||
// // STEP: A computes Z
|
||||
// AZᵤ, AZᵥ := p256.Add(Yᵤ, Yᵥ, vpwᵤ, new(big.Int).Neg(vpwᵥ))
|
||||
// AZᵤ, AZᵥ = p256.ScalarMult(AZᵤ, AZᵥ, α)
|
||||
// // STEP: A computes k
|
||||
// // H(pw,id_P,id_Q,X,Y,Z)
|
||||
// HA := sha256.New()
|
||||
// HA.Write(pw)
|
||||
// HA.Write(Xᵤ.Bytes())
|
||||
// HA.Write(Xᵥ.Bytes())
|
||||
// HA.Write(Yᵤ.Bytes())
|
||||
// HA.Write(Yᵥ.Bytes())
|
||||
// HA.Write(AZᵤ.Bytes())
|
||||
// HA.Write(AZᵥ.Bytes())
|
||||
// Ak := HA.Sum(nil)
|
||||
// // END
|
||||
// // verify
|
||||
// fmt.Println(Ak)
|
||||
// fmt.Println(Bk)
|
||||
// }
|
55
src/pake/pake_test.go
Normal file
55
src/pake/pake_test.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
package pake
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPake(t *testing.T) {
|
||||
// successful (both have same k)
|
||||
// initialize A
|
||||
A, err := Init([]byte{1, 2, 3}, 0, elliptic.P256())
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, A.IsVerified())
|
||||
// initialize B
|
||||
B, err := Init([]byte{1, 2, 3}, 1, elliptic.P256())
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, B.IsVerified())
|
||||
// send A's stuff to B
|
||||
err = B.Update(A.Bytes())
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, B.IsVerified())
|
||||
// send B's stuff to A
|
||||
err = A.Update(B.Bytes())
|
||||
assert.Nil(t, err) // A validates
|
||||
assert.True(t, A.IsVerified())
|
||||
// send A's stuff back to B
|
||||
err = B.Update(A.Bytes())
|
||||
assert.Nil(t, err) // B validates
|
||||
assert.True(t, B.IsVerified())
|
||||
|
||||
// failure (both have different k)
|
||||
// initialize A
|
||||
A, err = Init([]byte{1, 2, 3}, 0, elliptic.P256())
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, A.IsVerified())
|
||||
// initialize B
|
||||
B, err = Init([]byte{4, 5, 6}, 1, elliptic.P256())
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, B.IsVerified())
|
||||
// send A's stuff to B
|
||||
err = B.Update(A.Bytes())
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, B.IsVerified())
|
||||
// send B's stuff to A
|
||||
err = A.Update(B.Bytes())
|
||||
assert.NotNil(t, err) // A validates
|
||||
assert.False(t, A.IsVerified())
|
||||
// send A's stuff back to B
|
||||
err = B.Update(A.Bytes())
|
||||
assert.NotNil(t, err)
|
||||
assert.False(t, B.IsVerified())
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue