From 180d48679d272cb9a3460064e733abed118e2b64 Mon Sep 17 00:00:00 2001 From: Linus Gasser Date: Thu, 16 Dec 2021 16:05:38 +0100 Subject: [PATCH 1/2] Added a test with outside signature --- calypso/api_test.go | 125 +++++++++++++++++++++++++++++++++++++++++++ calypso/contracts.go | 46 ++++++++++++++++ calypso/struct.go | 76 +++++++++++++++++++++++++- 3 files changed, 246 insertions(+), 1 deletion(-) diff --git a/calypso/api_test.go b/calypso/api_test.go index a57a56bd67..55ae5475f5 100644 --- a/calypso/api_test.go +++ b/calypso/api_test.go @@ -2,6 +2,7 @@ package calypso import ( "encoding/binary" + "go.dedis.ch/kyber/v3/util/key" "testing" "time" @@ -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 @@ -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) +} diff --git a/calypso/contracts.go b/calypso/contracts.go index ef5ba8df2b..293a18db12 100644 --- a/calypso/contracts.go +++ b/calypso/contracts.go @@ -2,6 +2,7 @@ package calypso import ( "fmt" + "go.dedis.ch/kyber/v3" "strings" "go.dedis.ch/cothority/v3" @@ -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 +} diff --git a/calypso/struct.go b/calypso/struct.go index 415a9c0263..fb87397d08 100644 --- a/calypso/struct.go +++ b/calypso/struct.go @@ -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() { @@ -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 { @@ -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 } From d5adf911001eb348195b1900c9e29ee20d3638f2 Mon Sep 17 00:00:00 2001 From: Linus Gasser Date: Wed, 22 Dec 2021 09:24:57 +0100 Subject: [PATCH 2/2] don't use calypsoClient.ltsReply --- calypso/api_test.go | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/calypso/api_test.go b/calypso/api_test.go index 55ae5475f5..c718e91aab 100644 --- a/calypso/api_test.go +++ b/calypso/api_test.go @@ -138,8 +138,6 @@ func TestClient_Calypso(t *testing.T) { 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 for data point #1 darc1 := darc.NewDarc(darc.InitRules([]darc.Identity{provider1.Identity()}, @@ -171,8 +169,8 @@ func TestClient_Calypso(t *testing.T) { //Create a secret key key1 := []byte("secret key 1") //Create a Write instance - write1 := NewWrite(cothority.Suite, calypsoClient.ltsReply.InstanceID, - darc1.GetBaseID(), calypsoClient.ltsReply.X, key1) + write1 := NewWrite(cothority.Suite, ltsReply.InstanceID, + darc1.GetBaseID(), ltsReply.X, key1) //Write it to calypso wr1, err := calypsoClient.AddWrite(write1, provider1, 1, *darc1, 10) require.NoError(t, err) @@ -190,8 +188,8 @@ func TestClient_Calypso(t *testing.T) { key2 := []byte("secret key 2") //Create a Write instance - write2 := NewWrite(cothority.Suite, calypsoClient.ltsReply.InstanceID, - darc2.GetBaseID(), calypsoClient.ltsReply.X, key2) + write2 := NewWrite(cothority.Suite, ltsReply.InstanceID, + darc2.GetBaseID(), ltsReply.X, key2) wr2, err := calypsoClient.AddWrite(write2, provider2, 1, *darc2, 10) require.NoError(t, err) prWr2, err := calypsoClient.WaitProof(wr2.InstanceID, time.Second, nil) @@ -212,7 +210,7 @@ func TestClient_Calypso(t *testing.T) { // Make sure you can actually decrypt dk1, err := calypsoClient.DecryptKey(&DecryptKey{Read: *prRe1, Write: *prWr1}) require.NoError(t, err) - require.True(t, dk1.X.Equal(calypsoClient.ltsReply.X)) + require.True(t, dk1.X.Equal(ltsReply.X)) keyCopy1, err := dk1.RecoverKey(reader1.Ed25519.Secret) require.NoError(t, err) require.Equal(t, key1, keyCopy1) @@ -256,8 +254,6 @@ func TestClient_Calypso_Simple(t *testing.T) { 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()}, @@ -278,8 +274,8 @@ func TestClient_Calypso_Simple(t *testing.T) { 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) + ltsReply.InstanceID, + userDarc.GetBaseID(), ltsReply.X, data) require.NoError(t, err) // Create a write-instance and send it to Byzcoin - here @@ -332,7 +328,7 @@ func TestClient_Calypso_Simple(t *testing.T) { dk, err := calypsoClient.DecryptKey(&DecryptKey{Read: *proofRd, Write: *proofWr}) require.NoError(t, err) - require.True(t, dk.X.Equal(calypsoClient.ltsReply.X)) + require.True(t, dk.X.Equal(ltsReply.X)) keyCopy, err := dk.RecoverKey(ephemeral.Private) require.NoError(t, err) var wrCopy Write