Skip to content
Open
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
88 changes: 75 additions & 13 deletions tlock.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,29 +137,54 @@ func (t Tlock) Metadata(dst io.Writer) (err error) {
// TimeLock encrypts the specified data for the given round number. The data
// can't be decrypted until the specified round is reached by the network in use.
func TimeLock(scheme crypto.Scheme, publicKey kyber.Point, roundNumber uint64, data []byte) (*ibe.Ciphertext, error) {
return TimeLockWithAdditionalData(scheme, publicKey, roundNumber, data, nil)
}

// TimeLockWithAdditionalData encrypts data with additional context to prevent replay attacks.
// The additional data is embedded in the plaintext before encryption.
func TimeLockWithAdditionalData(scheme crypto.Scheme, publicKey kyber.Point, roundNumber uint64, data []byte, additionalData []byte) (*ibe.Ciphertext, error) {
if publicKey.Equal(publicKey.Clone().Null()) {
return nil, ErrInvalidPublicKey
}

id := scheme.DigestBeacon(&chain.Beacon{
// Create beacon
beacon := &chain.Beacon{
Round: roundNumber,
})
}

// Get the ID from the beacon
id := scheme.DigestBeacon(beacon)

// Prepend additional data to plaintext if provided
var dataToEncrypt []byte
if len(additionalData) > 0 {
dataToEncrypt = make([]byte, 4+len(additionalData)+len(data))
// Store length (4 bytes, big-endian)
dataToEncrypt[0] = byte(len(additionalData) >> 24)
dataToEncrypt[1] = byte(len(additionalData) >> 16)
dataToEncrypt[2] = byte(len(additionalData) >> 8)
dataToEncrypt[3] = byte(len(additionalData))
copy(dataToEncrypt[4:], additionalData)
copy(dataToEncrypt[4+len(additionalData):], data)
} else {
dataToEncrypt = data
}

var cipherText *ibe.Ciphertext
var err error
switch scheme.Name {
case crypto.ShortSigSchemeID:
// the ShortSigSchemeID uses the wrong DST for G1, so we keep it for retro-compatibility
cipherText, err = ibe.EncryptCCAonG2(bls.NewBLS12381SuiteWithDST(bls.DefaultDomainG2(), bls.DefaultDomainG2()), publicKey, id, data)
cipherText, err = ibe.EncryptCCAonG2(bls.NewBLS12381SuiteWithDST(bls.DefaultDomainG2(), bls.DefaultDomainG2()), publicKey, id, dataToEncrypt)
case crypto.UnchainedSchemeID:
cipherText, err = ibe.EncryptCCAonG1(bls.NewBLS12381Suite(), publicKey, id, data)
cipherText, err = ibe.EncryptCCAonG1(bls.NewBLS12381Suite(), publicKey, id, dataToEncrypt)
case crypto.SigsOnG1ID:
cipherText, err = ibe.EncryptCCAonG2(bls.NewBLS12381Suite(), publicKey, id, data)
cipherText, err = ibe.EncryptCCAonG2(bls.NewBLS12381Suite(), publicKey, id, dataToEncrypt)
case crypto.BN254UnchainedOnG1SchemeID:
suite := bn.NewSuiteBn254()
suite.SetDomainG1([]byte("BLS_SIG_BN254G1_XMD:KECCAK-256_SVDW_RO_NUL_"))
suite.SetDomainG2([]byte("BLS_SIG_BN254G2_XMD:KECCAK-256_SVDW_RO_NUL_"))
cipherText, err = ibe.EncryptCCAonG2(suite, publicKey, id, data)
cipherText, err = ibe.EncryptCCAonG2(suite, publicKey, id, dataToEncrypt)
default:
return nil, fmt.Errorf("unsupported drand scheme '%s'", scheme.Name)
}
Expand All @@ -174,32 +199,41 @@ func TimeLock(scheme crypto.Scheme, publicKey kyber.Point, roundNumber uint64, d
// TimeUnlock decrypts the specified ciphertext for the given beacon. The
// ciphertext can't be decrypted until the specified round is reached by the network in use.
func TimeUnlock(scheme crypto.Scheme, publicKey kyber.Point, beacon chain.Beacon, ciphertext *ibe.Ciphertext) ([]byte, error) {
return TimeUnlockWithAdditionalData(scheme, publicKey, beacon, ciphertext, nil)
}

// TimeUnlockWithAdditionalData decrypts ciphertext and verifies the embedded additional data.
func TimeUnlockWithAdditionalData(scheme crypto.Scheme, publicKey kyber.Point, beacon chain.Beacon, ciphertext *ibe.Ciphertext, additionalData []byte) ([]byte, error) {
// Verify the beacon first
if err := scheme.VerifyBeacon(&beacon, publicKey); err != nil {
return nil, fmt.Errorf("verify beacon: %w", err)
}

var data []byte
// Perform standard IBE decryption
var decryptedData []byte
var err error
switch scheme.Name {
case crypto.ShortSigSchemeID:
var signature bls.KyberG1
if err := signature.UnmarshalBinary(beacon.Signature); err != nil {
return nil, fmt.Errorf("unmarshal kyber G1: %w", err)
}
// the ShortSigSchemeID uses the wrong DST for G1, so we keep it for retro-compatibility
data, err = ibe.DecryptCCAonG2(bls.NewBLS12381SuiteWithDST(bls.DefaultDomainG2(), bls.DefaultDomainG2()), &signature, ciphertext)
suite := bls.NewBLS12381SuiteWithDST(bls.DefaultDomainG2(), bls.DefaultDomainG2())
decryptedData, err = ibe.DecryptCCAonG2(suite, &signature, ciphertext)
case crypto.UnchainedSchemeID:
var signature bls.KyberG2
if err := signature.UnmarshalBinary(beacon.Signature); err != nil {
return nil, fmt.Errorf("unmarshal kyber G2: %w", err)
}
data, err = ibe.DecryptCCAonG1(bls.NewBLS12381Suite(), &signature, ciphertext)
suite := bls.NewBLS12381Suite()
decryptedData, err = ibe.DecryptCCAonG1(suite, &signature, ciphertext)
case crypto.SigsOnG1ID:
var signature bls.KyberG1
if err := signature.UnmarshalBinary(beacon.Signature); err != nil {
return nil, fmt.Errorf("unmarshal kyber G1: %w", err)
}
data, err = ibe.DecryptCCAonG2(bls.NewBLS12381Suite(), &signature, ciphertext)
suite := bls.NewBLS12381Suite()
decryptedData, err = ibe.DecryptCCAonG2(suite, &signature, ciphertext)
case crypto.BN254UnchainedOnG1SchemeID:
suite := bn.NewSuiteBn254()
suite.SetDomainG1([]byte("BLS_SIG_BN254G1_XMD:KECCAK-256_SVDW_RO_NUL_"))
Expand All @@ -208,7 +242,7 @@ func TimeUnlock(scheme crypto.Scheme, publicKey kyber.Point, beacon chain.Beacon
if err := signature.UnmarshalBinary(beacon.Signature); err != nil {
return nil, fmt.Errorf("unmarshal kyber G1: %w", err)
}
data, err = ibe.DecryptCCAonG2(suite, signature, ciphertext)
decryptedData, err = ibe.DecryptCCAonG2(suite, signature, ciphertext)
default:
return nil, fmt.Errorf("unsupported drand scheme '%s'", scheme.Name)
}
Expand All @@ -217,7 +251,35 @@ func TimeUnlock(scheme crypto.Scheme, publicKey kyber.Point, beacon chain.Beacon
return nil, fmt.Errorf("decrypt dek: %w", err)
}

return data, nil
// Extract and verify additional data if present
if len(decryptedData) >= 4 {
potentialAdLen := int(decryptedData[0])<<24 | int(decryptedData[1])<<16 | int(decryptedData[2])<<8 | int(decryptedData[3])

if potentialAdLen > 0 && potentialAdLen < len(decryptedData) && len(decryptedData) >= 4+potentialAdLen {
if len(additionalData) == 0 {
return nil, fmt.Errorf("ciphertext contains additional data but none was provided for verification")
}

if potentialAdLen != len(additionalData) {
return nil, fmt.Errorf("additional data length mismatch: expected %d, got %d", len(additionalData), potentialAdLen)
}

embeddedAdditionalData := decryptedData[4 : 4+potentialAdLen]
for i, b := range embeddedAdditionalData {
if b != additionalData[i] {
return nil, fmt.Errorf("additional data mismatch at byte %d", i)
}
}

return decryptedData[4+potentialAdLen:], nil
}
}

if len(additionalData) > 0 {
return nil, fmt.Errorf("additional data was provided but ciphertext does not contain any")
}

return decryptedData, nil
}

// =============================================================================
Expand Down