Skip to content

[group key addrs 2/?]: internal/ecies: add encrypt/decrypt with ECIES #1512

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

guggero
Copy link
Member

@guggero guggero commented May 6, 2025

Implements the ECIES encryption scheme as mentioned here: #874 (comment)

This commit creates a simple encryption and decryption function that uses ChaCha20Poly1305 for tne symmetric encryption and HKDF with SHA256 for the key derivation.
The shared key generation is not part of these functions, because we'll need to use lnd RPCs in some cases to be able to derive it.

The idea is that we'd transport the sender's ephemeral public key outside of the cipher text and use it as the additional data to authenticate the ciphertext.


This change is Reviewable

@guggero guggero requested a review from Roasbeef May 6, 2025 08:53
@coveralls
Copy link

coveralls commented May 6, 2025

Pull Request Test Coverage Report for Build 15299877521

Details

  • 67 of 90 (74.44%) changed or added relevant lines in 1 file are covered.
  • 41 unchanged lines in 8 files lost coverage.
  • Overall coverage increased (+0.05%) to 37.194%

Changes Missing Coverage Covered Lines Changed/Added Lines %
internal/ecies/ecies.go 67 90 74.44%
Files with Coverage Reduction New Missed Lines %
commitment/tap.go 1 72.05%
address/address.go 2 69.55%
address/mock.go 2 96.08%
asset/group_key.go 2 57.89%
tappsbt/create.go 2 26.74%
asset/mock.go 5 64.71%
tapgarden/caretaker.go 7 68.21%
asset/asset.go 20 45.46%
Totals Coverage Status
Change from base Build 15279785074: 0.05%
Covered Lines: 27061
Relevant Lines: 72757

💛 - Coveralls

@levmi levmi added the P1 label May 8, 2025
@levmi levmi added this to the v0.7 milestone May 8, 2025
@levmi levmi moved this from 🆕 New to 👀 In review in Taproot-Assets Project Board May 8, 2025
@lightninglabs-deploy
Copy link

@Roasbeef: review reminder

var result bytes.Buffer
result.Write(nonce)

gcm, err := cipher.NewGCMWithNonceSize(block, GCMNonceSize)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason to use this over NewGCM? 12-byte nonces are standardized, it's also what the underlying Go packages uses with the default constructor.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See section 5.2.1 of this PDF: https://nvlpubs.nist.gov/nistpubs/legacy/sp/nistspecialpublication800-38d.pdf

For IVs, it is recommended that implementations restrict support to the length of 96 bits, to
promote interoperability, efficiency, and simplicity of design.

IV here is basically a nonce.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One alternative crypto system to consider is chacha20poly1305: https://pkg.go.dev/golang.org/x/crypto/chacha20poly1305#New. This is what we use for encryption in LN. It also natively supports adding associative data (plaintext data included in the MAC).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I just used an example I found somewhere.
But I like ChaCha20Poly1305 better, changed the code to use that. We'll use the sender's ephemeral public key as the additional data, to authenticate the ciphertext. It will be part of the outer (authmailbox message) envelope, not the ciphertext itself. Does that make sense?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll use the sender's ephemeral public key as the additional data, to authenticate the ciphertext

Makes sense, this is exactly what I had in mind!

When it comes to the question of if we should have the key be a "part" of the cipher text (sent as a single blob), or split out into a single key, I lean towards making it part of the cipher text. That way it's a logical unit during transport.

Copy link
Contributor

@gijswijs gijswijs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made some small comments throughout.

// SHA256.
func HkdfSha256(secret []byte) ([32]byte, error) {
var key [32]byte
kdf := hkdf.New(sha256.New, secret, nil, nil)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a comment here explaining why it's ok to not use a salt here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, adding some salt here wouldn't be too bad of an idea. Added the protocol name to make the derived key unique to this application.

func EncryptSha256ChaCha20Poly1305(sharedSecret [32]byte, msg []byte,
additionalData []byte) ([]byte, error) {

// We begin by stretching the shared secret using HKDF with SHA256.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a lot of stretching involved if it goes from 32 bytes to 32 bytes. 😄

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, you're right. Re-formulated to "hardening against brute forcing".

Copy link
Member

@Roasbeef Roasbeef left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking pretty good!

}

// Select a random nonce, and leave capacity for the ciphertext.
nonce := make(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we're using a random nonce here, we should use NewX, which is meant for cases where a counter-like nonce isn't used. Basically some extra security margin for when nonces are generated randomly.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes, makes sense. Changed to X.

Copy link
Member

@Roasbeef Roasbeef left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 3 of 3 files at r1, all commit messages.
Reviewable status: all files reviewed, 5 unresolved discussions (waiting on @guggero)

This commit creates a simple encryption and decryption function that
uses ChaCha20Poly1305 for tne symmetric encryption and HKDF with SHA256 for
the key derivation.
The shared key generation is not part of these functions, because we'll
need to use lnd RPCs in some cases to be able to derive it.
Copy link
Contributor

@gijswijs gijswijs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Should we be worried about the TestLightningTerminal/custom_channels_multi_rfq (119.50s) litd itest failing?

@guggero
Copy link
Member Author

guggero commented May 28, 2025

Should we be worried about the TestLightningTerminal/custom_channels_multi_rfq (119.50s) litd itest failing?

No, that looks like a flake. Re-triggered it.

@guggero guggero requested a review from Roasbeef May 28, 2025 19:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: 👀 In review
Development

Successfully merging this pull request may close these issues.

6 participants