Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
186 changes: 185 additions & 1 deletion encrypt/ibe/ibe.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,94 @@ func DecryptCCAonG1(s pairing.Suite, private kyber.Point, c *Ciphertext) ([]byte
return msg, nil
}

// EncryptCCAonG1WithAD encrypts with additional data bound via commitment.
// different AD values produce different ciphertexts for replay attack prevention.
func EncryptCCAonG1WithAD(s pairing.Suite, master kyber.Point, ID, msg, additionalData []byte) (*Ciphertext, error) {
Comment thread
alienx5499 marked this conversation as resolved.
if len(msg) > s.Hash().Size() {
return nil, errors.New("plaintext too long for the hash function provided")
}

// 1. Compute Gid = e(master,Q_id)
hG2, ok := s.G2().Point().(kyber.HashablePoint)
if !ok {
return nil, errors.New("point needs to implement `kyber.HashablePoint`")
}
Qid := hG2.Hash(ID)
Gid := s.Pair(master, Qid)

// 2. Derive random sigma
sigma := make([]byte, len(msg))
if _, err := rand.Read(sigma); err != nil {
return nil, fmt.Errorf("err reading rand sigma: %v", err)
}
// 3. derive r with additional data included in hash
r, err := h3WithAD(s, sigma, msg, additionalData)
if err != nil {
return nil, err
}
// 4. Compute U = rP
U := s.G1().Point().Mul(r, nil)

// 5. Compute V = sigma XOR H2(rGid)
rGid := Gid.Mul(r, Gid) // even in Gt, it's additive notation
hrGid, err := gtToHash(s, rGid, len(msg))
if err != nil {
return nil, err
}
V := xor(sigma, hrGid)

// 6. Compute M XOR H(sigma)
hsigma, err := h4(s, sigma, len(msg))
if err != nil {
return nil, err
}
W := xor(msg, hsigma)

return &Ciphertext{
U: U,
V: V,
W: W,
}, nil
}

// DecryptCCAonG1WithAD decrypts with additional data verification.
// wrong AD causes cryptographic failure at rP check.
func DecryptCCAonG1WithAD(s pairing.Suite, private kyber.Point, c *Ciphertext, additionalData []byte) ([]byte, error) {
if len(c.W) > s.Hash().Size() {
return nil, errors.New("ciphertext too long for the hash function provided")
}

// 1. Compute sigma = V XOR H2(e(rP,private))
rGid := s.Pair(c.U, private)
hrGid, err := gtToHash(s, rGid, len(c.W))
if err != nil {
return nil, err
}
if len(hrGid) != len(c.V) {
return nil, fmt.Errorf("XorSigma is of invalid length: exp %d vs got %d", len(hrGid), len(c.V))
}
sigma := xor(hrGid, c.V)

// 2. Compute M = W XOR H4(sigma)
hsigma, err := h4(s, sigma, len(c.W))
if err != nil {
return nil, err
}

msg := xor(hsigma, c.W)

// 3. Check U = rP with additional data
r, err := h3WithAD(s, sigma, msg, additionalData)
if err != nil {
return nil, err
}
rP := s.G1().Point().Mul(r, nil)
if !rP.Equal(c.U) {
return nil, fmt.Errorf("invalid proof: rP check failed")
}
return msg, nil
}

// EncryptCCAonG2 implements the CCA identity-based encryption scheme from
// https://crypto.stanford.edu/~dabo/pubs/papers/bfibe.pdf for more information
// about the scheme.
Expand Down Expand Up @@ -227,8 +315,101 @@ func DecryptCCAonG2(s pairing.Suite, private kyber.Point, c *Ciphertext) ([]byte
return msg, nil
}

// EncryptCCAonG2WithAD encrypts with additional data bound via commitment.
// different AD values produce different ciphertexts for replay attack prevention.
func EncryptCCAonG2WithAD(s pairing.Suite, master kyber.Point, ID, msg, additionalData []byte) (*Ciphertext, error) {
if len(msg) > s.Hash().Size() {
return nil, errors.New("plaintext too long for the hash function provided")
}

// 1. Compute Gid = e(Q_id, master)
hG2, ok := s.G1().Point().(kyber.HashablePoint)
if !ok {
return nil, errors.New("point needs to implement `kyber.HashablePoint`")
}
Qid := hG2.Hash(ID)
Gid := s.Pair(Qid, master)

// 2. Derive random sigma
sigma := make([]byte, len(msg))
if _, err := rand.Read(sigma); err != nil {
return nil, fmt.Errorf("err reading rand sigma: %v", err)
}
// 3. Derive r from sigma, msg, and additionalData
r, err := h3WithAD(s, sigma, msg, additionalData)
if err != nil {
return nil, err
}
// 4. Compute U = rP
U := s.G2().Point().Mul(r, nil)

// 5. Compute V = sigma XOR H2(rGid)
rGid := Gid.Mul(r, Gid) // even in Gt, it's additive notation
hrGid, err := gtToHash(s, rGid, len(msg))
if err != nil {
return nil, err
}
V := xor(sigma, hrGid)

// 6. Compute M XOR H(sigma)
hsigma, err := h4(s, sigma, len(msg))
if err != nil {
return nil, err
}
W := xor(msg, hsigma)

return &Ciphertext{
U: U,
V: V,
W: W,
}, nil
}

// DecryptCCAonG2WithAD decrypts with additional data verification.
// wrong AD causes cryptographic failure at rP check.
func DecryptCCAonG2WithAD(s pairing.Suite, private kyber.Point, c *Ciphertext, additionalData []byte) ([]byte, error) {
if len(c.W) > s.Hash().Size() {
return nil, errors.New("ciphertext too long for the hash function provided")
}

// 1. Compute sigma = V XOR H2(e(rP,private))
rGid := s.Pair(private, c.U)
hrGid, err := gtToHash(s, rGid, len(c.W))
if err != nil {
return nil, err
}
if len(hrGid) != len(c.V) {
return nil, fmt.Errorf("XorSigma is of invalid length: exp %d vs got %d", len(hrGid), len(c.V))
}
sigma := xor(hrGid, c.V)

// 2. Compute M = W XOR H4(sigma)
hsigma, err := h4(s, sigma, len(c.W))
if err != nil {
return nil, err
}

msg := xor(hsigma, c.W)

// 3. verify rP check with additional data
r, err := h3WithAD(s, sigma, msg, additionalData)
if err != nil {
return nil, err
}
rP := s.G2().Point().Mul(r, nil)
if !rP.Equal(c.U) {
return nil, fmt.Errorf("invalid proof: rP check failed on msg %s, r %x", msg, r)
}
return msg, nil
}

// hash sigma and msg to get r
func h3(s pairing.Suite, sigma, msg []byte) (kyber.Scalar, error) {
return h3WithAD(s, sigma, msg, nil)
}

// h3WithAD includes additional data in the hash for r generation
func h3WithAD(s pairing.Suite, sigma, msg, additionalData []byte) (kyber.Scalar, error) {
h := s.Hash()

if h.Size() != s.G1().ScalarLen() {
Expand All @@ -242,7 +423,10 @@ func h3(s pairing.Suite, sigma, msg []byte) (kyber.Scalar, error) {
return nil, fmt.Errorf("err hashing sigma: %v", err)
}
_, _ = h.Write(msg)
// we hash it a first time: buffer = hash("IBE-H3" || sigma || msg)
if len(additionalData) > 0 {
_, _ = h.Write(additionalData)
}
// hash("IBE-H3" || sigma || msg || additionalData)
buffer := h.Sum(nil)

hashable, ok := s.G1().Scalar().(*mod.Int)
Expand Down
174 changes: 174 additions & 0 deletions encrypt/ibe/ibe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,3 +259,177 @@ func TestCPAEncryptOnG1(t *testing.T) {
require.NoError(t, err)
require.Equal(t, msg, msg2)
}

func TestEncryptDecryptWithAdditionalData(t *testing.T) {
t.Run("OnG1_WithCorrectAD", func(t *testing.T) {
suite, Ppub, ID, sQid, _, _ := newSetting(1)
msg := []byte("Hello World\n")
additionalData := []byte("transaction_hash_0x1234")

c, err := EncryptCCAonG1WithAD(suite, Ppub, ID, msg, additionalData)
require.NoError(t, err)
msg2, err := DecryptCCAonG1WithAD(suite, sQid, c, additionalData)
require.NoError(t, err)
require.Equal(t, msg, msg2)
})

t.Run("OnG2_WithCorrectAD", func(t *testing.T) {
suite, Ppub, ID, sQid, _, _ := newSetting(2)
msg := []byte("Hello World\n")
additionalData := []byte("transaction_hash_0x1234")

c, err := EncryptCCAonG2WithAD(suite, Ppub, ID, msg, additionalData)
require.NoError(t, err)
msg2, err := DecryptCCAonG2WithAD(suite, sQid, c, additionalData)
require.NoError(t, err)
require.Equal(t, msg, msg2)
})

t.Run("OnG1_WithIncorrectAD_FailsDecryption", func(t *testing.T) {
suite, Ppub, ID, sQid, _, _ := newSetting(1)
msg := []byte("Hello World\n")
correctAD := []byte("transaction_hash_0x1234")
incorrectAD := []byte("transaction_hash_0x5678")

c, err := EncryptCCAonG1WithAD(suite, Ppub, ID, msg, correctAD)
require.NoError(t, err)

// Attempt to decrypt with incorrect additional data should fail
_, err = DecryptCCAonG1WithAD(suite, sQid, c, incorrectAD)
require.Error(t, err)
require.ErrorContains(t, err, "invalid proof")
})

t.Run("OnG2_WithIncorrectAD_FailsDecryption", func(t *testing.T) {
suite, Ppub, ID, sQid, _, _ := newSetting(2)
msg := []byte("Hello World\n")
correctAD := []byte("transaction_hash_0x1234")
incorrectAD := []byte("transaction_hash_0x5678")

c, err := EncryptCCAonG2WithAD(suite, Ppub, ID, msg, correctAD)
require.NoError(t, err)

// Attempt to decrypt with incorrect additional data should fail
_, err = DecryptCCAonG2WithAD(suite, sQid, c, incorrectAD)
require.Error(t, err)
require.ErrorContains(t, err, "invalid proof")
})

t.Run("OnG1_WithMissingAD_FailsDecryption", func(t *testing.T) {
suite, Ppub, ID, sQid, _, _ := newSetting(1)
msg := []byte("Hello World\n")
additionalData := []byte("transaction_hash_0x1234")

c, err := EncryptCCAonG1WithAD(suite, Ppub, ID, msg, additionalData)
require.NoError(t, err)

// Attempt to decrypt without additional data should fail
_, err = DecryptCCAonG1WithAD(suite, sQid, c, nil)
require.Error(t, err)
require.ErrorContains(t, err, "invalid proof")
})

t.Run("OnG2_WithMissingAD_FailsDecryption", func(t *testing.T) {
suite, Ppub, ID, sQid, _, _ := newSetting(2)
msg := []byte("Hello World\n")
additionalData := []byte("transaction_hash_0x1234")

c, err := EncryptCCAonG2WithAD(suite, Ppub, ID, msg, additionalData)
require.NoError(t, err)

// Attempt to decrypt without additional data should fail
_, err = DecryptCCAonG2WithAD(suite, sQid, c, nil)
require.Error(t, err)
require.ErrorContains(t, err, "invalid proof")
})

t.Run("OnG1_WithEmptyAD", func(t *testing.T) {
suite, Ppub, ID, sQid, _, _ := newSetting(1)
msg := []byte("Hello World\n")
emptyAD := []byte{}

c, err := EncryptCCAonG1WithAD(suite, Ppub, ID, msg, emptyAD)
require.NoError(t, err)
msg2, err := DecryptCCAonG1WithAD(suite, sQid, c, emptyAD)
require.NoError(t, err)
require.Equal(t, msg, msg2)
})

t.Run("OnG2_WithEmptyAD", func(t *testing.T) {
suite, Ppub, ID, sQid, _, _ := newSetting(2)
msg := []byte("Hello World\n")
emptyAD := []byte{}

c, err := EncryptCCAonG2WithAD(suite, Ppub, ID, msg, emptyAD)
require.NoError(t, err)
msg2, err := DecryptCCAonG2WithAD(suite, sQid, c, emptyAD)
require.NoError(t, err)
require.Equal(t, msg, msg2)
})
}

func TestDifferentADProducesDifferentCiphertexts(t *testing.T) {
t.Run("OnG1", func(t *testing.T) {
suite, Ppub, ID, _, _, _ := newSetting(1)
msg := []byte("Hello World\n")
ad1 := []byte("context1")
ad2 := []byte("context2")

c1, err := EncryptCCAonG1WithAD(suite, Ppub, ID, msg, ad1)
require.NoError(t, err)
c2, err := EncryptCCAonG1WithAD(suite, Ppub, ID, msg, ad2)
require.NoError(t, err)

// Different AD should produce different U points (due to different r values)
require.False(t, c1.U.Equal(c2.U), "Different AD should produce different ciphertexts")
})

t.Run("OnG2", func(t *testing.T) {
suite, Ppub, ID, _, _, _ := newSetting(2)
msg := []byte("Hello World\n")
ad1 := []byte("context1")
ad2 := []byte("context2")

c1, err := EncryptCCAonG2WithAD(suite, Ppub, ID, msg, ad1)
require.NoError(t, err)
c2, err := EncryptCCAonG2WithAD(suite, Ppub, ID, msg, ad2)
require.NoError(t, err)

// Different AD should produce different U points (due to different r values)
require.False(t, c1.U.Equal(c2.U), "Different AD should produce different ciphertexts")
})
}

func TestReplayAttackPrevention(t *testing.T) {
t.Run("OnG1_SameCiphertextDifferentContext", func(t *testing.T) {
suite, Ppub, ID, sQid, _, _ := newSetting(1)
msg := []byte("payment:100USD")
context1 := []byte("recipient:alice")
context2 := []byte("recipient:bob")

// Encrypt payment for Alice
ciphertext, err := EncryptCCAonG1WithAD(suite, Ppub, ID, msg, context1)
require.NoError(t, err)

// Try to "replay" the ciphertext in Bob's context - should fail
_, err = DecryptCCAonG1WithAD(suite, sQid, ciphertext, context2)
require.Error(t, err)
require.ErrorContains(t, err, "invalid proof")
})

t.Run("OnG2_SameCiphertextDifferentContext", func(t *testing.T) {
suite, Ppub, ID, sQid, _, _ := newSetting(2)
msg := []byte("payment:100USD")
context1 := []byte("recipient:alice")
context2 := []byte("recipient:bob")

// Encrypt payment for Alice
ciphertext, err := EncryptCCAonG2WithAD(suite, Ppub, ID, msg, context1)
require.NoError(t, err)

// Try to "replay" the ciphertext in Bob's context - should fail
_, err = DecryptCCAonG2WithAD(suite, sQid, ciphertext, context2)
require.Error(t, err)
require.ErrorContains(t, err, "invalid proof")
})
}