Skip to content

Commit

Permalink
Added a test with outside signature
Browse files Browse the repository at this point in the history
  • Loading branch information
ineiti committed Dec 21, 2021
1 parent 0d1b33d commit 180d486
Show file tree
Hide file tree
Showing 3 changed files with 246 additions and 1 deletion.
125 changes: 125 additions & 0 deletions calypso/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package calypso

import (
"encoding/binary"
"go.dedis.ch/kyber/v3/util/key"
"testing"
"time"

Expand Down Expand Up @@ -166,6 +167,7 @@ func TestClient_Calypso(t *testing.T) {
_, err = calypsoClient.SpawnDarc(admin, adminCt, gDarc, *darc2, 10)
adminCt++
require.NoError(t, err)

//Create a secret key
key1 := []byte("secret key 1")
//Create a Write instance
Expand Down Expand Up @@ -217,3 +219,126 @@ func TestClient_Calypso(t *testing.T) {

// use keyCopy to unlock the stuff in writeInstance.Data
}

// Tests the Calypso system with a simple write/read scenario.
// But it does the signing outside of the `Read` and `Write` methods for
// integration of the MPC signing by OneKey.
func TestClient_Calypso_Simple(t *testing.T) {
l := onet.NewTCPTest(cothority.Suite)
_, roster, _ := l.GenTree(3, true)
defer l.CloseAll()

admin := darc.NewSignerEd25519(nil, nil)
adminCt := uint64(1)
user := darc.NewSignerEd25519(nil, nil)
// Initialise the genesis message and send it to the service.
// The admin has the privilege to spawn darcs
msg, err := byzcoin.DefaultGenesisMsg(byzcoin.CurrentVersion, roster,
[]string{"spawn:" + ContractLongTermSecretID},
admin.Identity())

msg.BlockInterval = 500 * time.Millisecond
require.NoError(t, err)
// The darc inside it should be valid.
gDarc := msg.GenesisDarc
require.Nil(t, gDarc.Verify(true))
//Create Ledger
c, _, err := byzcoin.NewLedger(msg, false)
require.NoError(t, err)
//Create a Calypso Client (Byzcoin + Onet)
calypsoClient := NewClient(c)

//Create the LTS
for _, who := range roster.List {
err := calypsoClient.Authorize(who, c.ID)
require.NoError(t, err)
}
ltsReply, err := calypsoClient.CreateLTS(roster, gDarc.GetBaseID(), []darc.Signer{admin}, []uint64{adminCt})
adminCt++
require.NoError(t, err)
//If no error, assign it
calypsoClient.ltsReply = ltsReply

//Create a signer darc
userDarc := darc.NewDarc(darc.InitRules([]darc.Identity{user.Identity()},
[]darc.Identity{user.Identity()}), []byte("Provider1"))
// user can read and write.
// This can be changed to two different public keys.
err = userDarc.Rules.AddRule(darc.Action("spawn:"+ContractWriteID),
expression.InitOrExpr(user.Identity().String()))
require.NoError(t, err)
err = userDarc.Rules.AddRule(darc.Action("spawn:"+ContractReadID),
expression.InitOrExpr(user.Identity().String()))
require.NoError(t, err)
require.NotNil(t, userDarc)
_, err = calypsoClient.SpawnDarc(admin, adminCt, gDarc, *userDarc, 10)
adminCt++
require.NoError(t, err)

data := []byte("Some secret data - or the user's private key")
// Create a Write structure
write1, err := NewWriteData(cothority.Suite,
calypsoClient.ltsReply.InstanceID,
userDarc.GetBaseID(), calypsoClient.ltsReply.X, data)
require.NoError(t, err)

// Create a write-instance and send it to Byzcoin - here
// the instruction and the transaction is created manually,
// so that an external signer can sign the hash of the instruction.
wrInst, err := ContractWriteSpawnInstruction(write1, userDarc)
require.NoError(t, err)
wrInst.SignerCounter = []uint64{1}
wrInst.SignerIdentities = []darc.Identity{user.Identity()}
wrTx, err := calypsoClient.bcClient.CreateTransaction(*wrInst)
require.NoError(t, err)
digest := wrTx.Instructions.Hash()

// This signature can be replaced by an external signature.
signature, err := user.Sign(digest)
require.NoError(t, err)
wrTx.Instructions[0].Signatures = [][]byte{signature}

// Send the transaction to ByzCoin
_, err = calypsoClient.bcClient.AddTransactionAndWait(wrTx, 10)
require.NoError(t, err)
wrID := wrTx.Instructions[0].DeriveID("")
proofWr, err := calypsoClient.WaitProof(wrID, time.Second, nil)
require.NoError(t, err)

// Create a read-instance and send it to ByzCoin.
ephemeral := key.NewKeyPair(cothority.Suite)
readInst, err := ContractReadSpawnInstruction(wrID, ephemeral.Public)
require.NoError(t, err)
readInst.SignerCounter = []uint64{2}
readInst.SignerIdentities = []darc.Identity{user.Identity()}
readTx, err := calypsoClient.bcClient.CreateTransaction(*readInst)
require.NoError(t, err)
digest = readTx.Instructions.Hash()

// This signature can be replaced by an external signature
signature, err = user.Sign(digest)
require.NoError(t, err)
readTx.Instructions[0].Signatures = [][]byte{signature}
readID := readTx.Instructions[0].DeriveID("")

// Send the transaction to ByzCoin
_, err = calypsoClient.bcClient.AddTransactionAndWait(readTx, 10)
require.NoError(t, err)
proofRd, err := calypsoClient.WaitProof(readID, time.Second,
nil)
require.NoError(t, err)

// Make sure you can actually decrypt
dk, err := calypsoClient.DecryptKey(&DecryptKey{Read: *proofRd,
Write: *proofWr})
require.NoError(t, err)
require.True(t, dk.X.Equal(calypsoClient.ltsReply.X))
keyCopy, err := dk.RecoverKey(ephemeral.Private)
require.NoError(t, err)
var wrCopy Write
require.NoError(t, proofWr.VerifyAndDecode(cothority.Suite, ContractWriteID,
&wrCopy))
dataDecrypt, err := wrCopy.Decrypt(keyCopy)
require.NoError(t, err)
require.Equal(t, data, dataDecrypt)
}
46 changes: 46 additions & 0 deletions calypso/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package calypso

import (
"fmt"
"go.dedis.ch/kyber/v3"
"strings"

"go.dedis.ch/cothority/v3"
Expand Down Expand Up @@ -278,3 +279,48 @@ func (c ContractWrite) VerifyInstruction(rst byzcoin.ReadOnlyStateTrie, inst byz
}
return inst.VerifyWithOption(rst, ctxHash, nil)
}

// ContractWriteSpawnInstruction returns the spawn instruction for a Write
// contract.
func ContractWriteSpawnInstruction(wr *Write,
d *darc.Darc) (*byzcoin.Instruction, error) {
writeBuf, err := protobuf.Encode(wr)
if err != nil {
return nil, xerrors.Errorf("couldn't encode write: %v", err)
}
return &byzcoin.Instruction{
InstanceID: byzcoin.NewInstanceID(d.GetBaseID()),
Spawn: &byzcoin.Spawn{
ContractID: ContractWriteID,
Args: byzcoin.Arguments{
{
Name: "write",
Value: writeBuf,
},
},
},
}, nil
}

// ContractReadSpawnInstruction returns the spawn instruction for a Read
// contract.
func ContractReadSpawnInstruction(wrID byzcoin.InstanceID,
xc kyber.Point) (*byzcoin.Instruction, error) {
var readBuf []byte
read := &Read{
Write: wrID,
Xc: xc,
}
readBuf, err := protobuf.Encode(read)
if err != nil {
return nil, xerrors.Errorf("encoding Read message: %v", err)
}

return &byzcoin.Instruction{
InstanceID: wrID,
Spawn: &byzcoin.Spawn{
ContractID: ContractReadID,
Args: byzcoin.Arguments{{Name: "read", Value: readBuf}},
},
}, nil
}
76 changes: 75 additions & 1 deletion calypso/struct.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
package calypso

import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"fmt"

"go.dedis.ch/cothority/v3"
"go.dedis.ch/cothority/v3/byzcoin"
"go.dedis.ch/cothority/v3/darc"
"go.dedis.ch/kyber/v3"
"go.dedis.ch/kyber/v3/suites"
"go.dedis.ch/kyber/v3/util/random"
"go.dedis.ch/kyber/v3/xof/keccak"
"go.dedis.ch/onet/v3/network"
"golang.org/x/xerrors"
"io"
)

func init() {
Expand Down Expand Up @@ -68,6 +74,20 @@ func NewWrite(suite suites.Suite, ltsid byzcoin.InstanceID, writeDarc darc.ID, X
return wr
}

// NewWriteData is like NewWrite,
// but it encrypts the data with a random key and adds the data to the Write
// structure.
func NewWriteData(suite suites.Suite, ltsid byzcoin.InstanceID,
writeDarc darc.ID, X kyber.Point, data []byte) (wr *Write, err error) {
key := random.Bits(192, true, random.New())
wr = NewWrite(suite, ltsid, writeDarc, X, key)
wr.Data, err = AeadSeal(key, data)
if err != nil {
return nil, xerrors.Errorf("couldn't seal data: %v", err)
}
return
}

// CheckProof verifies that the write-request has actually been created with
// somebody having access to the secret key.
func (wr *Write) CheckProof(suite suite, writeID darc.ID) error {
Expand Down Expand Up @@ -97,6 +117,60 @@ func (wr *Write) CheckProof(suite suite, writeID darc.ID) error {
"%s\n%s", e.String(), wr.E.String())
}

// Decrypt calls AeadOpen to decrypt the data with the given key.
func (wr *Write) Decrypt(key []byte) ([]byte, error) {
return AeadOpen(key, wr.Data)
}

// This suggested length is from https://godoc.org/crypto/cipher#NewGCM example
const nonceLen = 12

// AeadSeal encrypts the given plaintext with the given key.
// It adds a 12-byte nonce to the ciphertext.
func AeadSeal(symKey, data []byte) ([]byte, error) {
block, err := aes.NewCipher(symKey)
if err != nil {
return nil,
xerrors.Errorf("creating aes cipher block instance: %v", err)
}

// Never use more than 2^32 random nonces with a given key because of the risk of a repeat.
nonce := make([]byte, nonceLen)
_, err = io.ReadFull(rand.Reader, nonce)
if err != nil {
return nil, xerrors.Errorf("reading nonce: %v", err)
}

aesgcm, err := cipher.NewGCM(block)
if err != nil {
return nil, xerrors.Errorf("creating aesgcm instance: %v", err)
}
encData := aesgcm.Seal(nil, nonce, data, nil)
encData = append(encData, nonce...)
return encData, nil
}

// AeadOpen decrypts a given ciphertext with the given key.
func AeadOpen(key, ciphertext []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil,
xerrors.Errorf("creating aes cipher block instance: %v", err)
}

aesgcm, err := cipher.NewGCM(block)
if err != nil {
return nil, xerrors.Errorf("creating aesgcm instance: %v", err)
}

if len(ciphertext) < 12 {
return nil, xerrors.New("ciphertext too short")
}
nonce := ciphertext[len(ciphertext)-nonceLen:]
out, err := aesgcm.Open(nil, nonce, ciphertext[0:len(ciphertext)-nonceLen], nil)
return out, cothority.ErrorOrNil(err, "decrypting ciphertext")
}

type newLtsConfig struct {
byzcoin.Proof
}
Expand Down

0 comments on commit 180d486

Please sign in to comment.