eciesrb 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8853c211ef575ae689839e550d6db8afd07a5442d7ac38ba459148cf72f9e6fb
4
+ data.tar.gz: 65b75a723ae0d3411064286a1e2d6637c12c20a1909581bc6a16f102e3bf801e
5
+ SHA512:
6
+ metadata.gz: cacd472056489a127ca14ebf199b7804ea5aedd67880cc7fc34becfb85579f3899d64750e2a64cb1d18a65f171de7792e0f5e5600f0c88e468c56e965adf8e06
7
+ data.tar.gz: 3de9f4265d674d6e285c9be76956ca615255cd6f339526b7ca3ecf9fe31fea9f985d7df25e3f521f60a616d48a5cd5d1db82dca2c46d6b88b72c465e31103297
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 0.0.1
4
+
5
+ - First alpha release
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Weiliang Li
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # eciesrb
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/eciesrb.svg)](https://badge.fury.io/rb/eciesrb)
4
+ [![License](https://img.shields.io/github/license/ecies/rb.svg)](https://github.com/ecies/rb)
5
+ [![CI](https://img.shields.io/github/actions/workflow/status/ecies/rb/ci.yml)](https://github.com/ecies/rb/actions)
6
+
7
+ Elliptic Curve Integrated Encryption Scheme for secp256k1 in Ruby.
8
+
9
+ This is a Ruby port of [eciespy](https://github.com/ecies/py).
10
+
11
+ ## Prerequisite
12
+
13
+ Make sure you have secp256k1 and openssl installed, if not:
14
+
15
+ ```bash
16
+ brew install secp256k1 openssl
17
+ ```
18
+
19
+ Then set environment variables:
20
+
21
+ ```bash
22
+ export C_INCLUDE_PATH=$(brew --prefix secp256k1)/include
23
+ ```
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ gem install eciesrb
29
+ ```
30
+
31
+ ## Quick Start
32
+
33
+ ```ruby
34
+ # examples/quickstart.rb
35
+ require "ecies"
36
+
37
+ # Generate a secret key
38
+ sk = Ecies.generate_key
39
+ raw_sk = Ecies.decode_hex(sk.send(:serialize))
40
+ raw_pk = sk.pubkey.serialize(compressed: false)
41
+
42
+ # Encrypt data with the receiver's public key
43
+ plaintext = "Hello, World!"
44
+ encrypted = Ecies.encrypt(raw_pk, plaintext)
45
+
46
+ # Decrypt data with the receiver's secret key
47
+ decrypted = Ecies.decrypt(raw_sk, encrypted)
48
+ puts decrypted # => "Hello, World!"
49
+ ```
50
+
51
+ ## Sponsors
52
+
53
+ <a href="https://dotenvx.com"><img alt="dotenvx" src="https://dotenvx.com/logo.png" width="200" height="200"/></a>
54
+
55
+ ## Configuration
56
+
57
+ You can customize the encryption behavior using a `Config` object:
58
+
59
+ ```ruby
60
+ # examples/config.rb
61
+ require "ecies"
62
+
63
+ Ecies::DEFAULT_CONFIG.is_ephemeral_key_compressed = true # Use compressed ephemeral public key
64
+ Ecies::DEFAULT_CONFIG.is_hkdf_key_compressed = true # Use compressed key for HKDF
65
+ Ecies::DEFAULT_CONFIG.symmetric_nonce_length = 16 # Nonce length for AES-GCM (default: 16)
66
+ ```
67
+
68
+ ### Configuration Parameters
69
+
70
+ - `is_ephemeral_key_compressed` (Boolean): Whether to use compressed format for the ephemeral public key. Default: `false`
71
+ - `is_hkdf_key_compressed` (Boolean): Whether to use compressed format for HKDF key derivation. Default: `false`
72
+ - `symmetric_nonce_length` (Integer): The nonce length for AES-GCM encryption. Options: `12`, `16`. Default: `16`
73
+
74
+ ## API Reference
75
+
76
+ ### `encrypt(receiver_pk, data, config = DEFAULT_CONFIG)`
77
+
78
+ Encrypts data using the receiver's public key.
79
+
80
+ **Parameters:**
81
+
82
+ - `receiver_pk` (String): The receiver's public key (raw bytes, serialized)
83
+ - `data` (String): The plaintext data to encrypt (raw bytes)
84
+ - `config` (Ecies::Config): Optional configuration object
85
+
86
+ **Returns:** (String) The encrypted data (ephemeral public key + encrypted data)
87
+
88
+ ### `decrypt(receiver_sk, data, config = DEFAULT_CONFIG)`
89
+
90
+ Decrypts data using the receiver's secret key.
91
+
92
+ **Parameters:**
93
+
94
+ - `receiver_sk` (String): The receiver's secret key (raw bytes, serialized)
95
+ - `data` (String): The encrypted data (ephemeral public key + encrypted data)
96
+ - `config` (Ecies::Config): Optional configuration object
97
+
98
+ **Returns:** (String) The decrypted plaintext data (raw bytes)
99
+
100
+ ## Changelog
101
+
102
+ See [CHANGELOG.md](./CHANGELOG.md).
data/eciesrb.gemspec ADDED
@@ -0,0 +1,14 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "eciesrb"
3
+ s.version = "0.0.1"
4
+ s.summary = "Elliptic Curve Integrated Encryption Scheme for secp256k1 in Ruby"
5
+ s.description = "Elliptic Curve Integrated Encryption Scheme for secp256k1 in Ruby, based on libsecp256k1 and OpenSSL."
6
+ s.authors = ["Weiliang Li"]
7
+ s.email = "to.be.impressive@gmail.com"
8
+ s.files = Dir["lib/**/**.rb"] + ["eciesrb.gemspec", "README.md", "LICENSE", "CHANGELOG.md"]
9
+ s.homepage = "https://github.com/ecies/rb"
10
+ s.license = "MIT"
11
+ s.add_dependency "libsecp256k1", "~> 0.6.1"
12
+ s.add_dependency "openssl", "~> 3.3"
13
+ s.required_ruby_version = ">= 3.2"
14
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ecies
4
+ # Configuration class for ECIES settings.
5
+ class Config
6
+ attr_accessor :is_ephemeral_key_compressed, :is_hkdf_key_compressed, :symmetric_nonce_length
7
+
8
+ def initialize(
9
+ is_ephemeral_key_compressed: false,
10
+ is_hkdf_key_compressed: false,
11
+ symmetric_nonce_length: 16
12
+ )
13
+ @is_ephemeral_key_compressed = is_ephemeral_key_compressed
14
+ @is_hkdf_key_compressed = is_hkdf_key_compressed
15
+ @symmetric_nonce_length = symmetric_nonce_length
16
+ end
17
+ end
18
+
19
+ DEFAULT_CONFIG = Config.new
20
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "secp256k1"
4
+ require_relative "symmetric"
5
+ require_relative "hash"
6
+
7
+ module Ecies
8
+ # Generates a new random Secp256k1 private key.
9
+ # @return [Secp256k1::PrivateKey] The generated private key.
10
+ def generate_key
11
+ loop do
12
+ sk = Secp256k1::PrivateKey.new
13
+ return sk
14
+ rescue ArgumentError
15
+ end
16
+ end
17
+
18
+ # Performs elliptic key encapsulation.
19
+ #
20
+ # @param private_key [String] Private key bytes
21
+ # @param peer_public_key [String] Peer's public key bytes
22
+ # @param is_compressed [Boolean] Whether to use compressed format (default: false)
23
+ #
24
+ # @return [String] HKDF-SHA256 derived 32-byte key
25
+ def encapsulate(private_key, peer_public_key, is_compressed = false)
26
+ sk = Secp256k1::PrivateKey.new(privkey: private_key, raw: true)
27
+ peer_pk = Secp256k1::PublicKey.new(pubkey: peer_public_key, raw: true)
28
+ shared_point = peer_pk.tweak_mul(private_key)
29
+
30
+ master = sk.pubkey.serialize(compressed: is_compressed) +
31
+ shared_point.serialize(compressed: is_compressed)
32
+ derive_key(master)
33
+ end
34
+
35
+ # Performs elliptic key decapsulation.
36
+ # @param public_key [String] Public key bytes
37
+ # @param peer_private_key [String] Peer's private key bytes
38
+ # @param is_compressed [Boolean] Whether to use compressed format (default: false)
39
+ # @return [String] HKDF-SHA256 derived 32-byte key
40
+ def decapsulate(public_key, peer_private_key, is_compressed = false)
41
+ pk = Secp256k1::PublicKey.new(pubkey: public_key, raw: true)
42
+ shared_point = pk.tweak_mul(peer_private_key)
43
+ master = pk.serialize(compressed: is_compressed) +
44
+ shared_point.serialize(compressed: is_compressed)
45
+ derive_key(master)
46
+ end
47
+
48
+ module_function :generate_key, :encapsulate, :decapsulate
49
+ end
data/lib/ecies/hash.rb ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+
5
+ module Ecies
6
+ # Derives a 32-byte key from the given master key using HKDF with SHA256.
7
+ #
8
+ # @param master [String] The input master key (binary string).
9
+ # @return [String] The derived 32-byte key (binary string).
10
+ def derive_key(master)
11
+ OpenSSL::KDF.hkdf(master, salt: "", info: "", length: 32, hash: "SHA256")
12
+ end
13
+
14
+ module_function :derive_key
15
+ end
data/lib/ecies/hex.rb ADDED
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ecies
4
+ # Decodes a hex string (with optional "0x" prefix) into raw bytes.
5
+ # @param [String] str The hex string to decode.
6
+ # return [String] The decoded raw bytes.
7
+ def decode_hex(str)
8
+ if str.start_with?("0x", "0X")
9
+ str = str[2..]
10
+ end
11
+ [str].pack("H*")
12
+ end
13
+
14
+ # Encodes raw bytes into a hex string without "0x" prefix.
15
+ # @param [String] bytes The raw bytes to encode.
16
+ # @return [String] The encoded hex string without "0x" prefix.
17
+ def encode_hex(bytes)
18
+ bytes.unpack1("H*")
19
+ end
20
+
21
+ module_function :decode_hex, :encode_hex
22
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+
5
+ module Ecies
6
+ AEAD_TAG_LENGTH = 16
7
+
8
+ # Encrypts plain text using symmetric AES-256-GCM encryption.
9
+ #
10
+ # @param algorithm [Symbol] The encryption algorithm to use (must be :aes-256-gcm)
11
+ # @param key [String] The encryption key (must be 32 bytes for AES-256)
12
+ # @param plain_text [String] The data to encrypt
13
+ # @param nonce_length [Integer] The length of the nonce/IV to generate
14
+ # @param aad [String] Additional authenticated data (optional, defaults to empty string)
15
+ #
16
+ # @return [String] The encrypted data formatted as: nonce + auth_tag + cipher_text
17
+ #
18
+ # @raise [ArgumentError] If the algorithm is not :aes-256-gcm
19
+ #
20
+ # @example
21
+ # key = OpenSSL::Random.random_bytes(32)
22
+ # encrypted = Ecies.sym_encrypt(:"aes-256-gcm", key, "Hello World", 12)
23
+ def sym_encrypt(algorithm, key, plain_text, nonce_length, aad = "")
24
+ if algorithm != :"aes-256-gcm"
25
+ raise ArgumentError, "Unsupported algorithm: #{algorithm}"
26
+ end
27
+
28
+ nonce = OpenSSL::Random.random_bytes(nonce_length)
29
+ cipher = OpenSSL::Cipher.new(algorithm.to_s).encrypt
30
+ cipher.key = key
31
+ cipher.iv_len = nonce_length
32
+ cipher.iv = nonce
33
+ cipher.auth_data = aad
34
+
35
+ cipher_text = cipher.update(plain_text) + cipher.final
36
+ tag = cipher.auth_tag
37
+ nonce + tag + cipher_text
38
+ end
39
+
40
+ # Decrypts cipher text that was encrypted using symmetric AES-256-GCM encryption.
41
+ #
42
+ # @param algorithm [Symbol] The encryption algorithm to use (must be :aes-256-gcm)
43
+ # @param key [String] The decryption key (must match the encryption key)
44
+ # @param cipher_text [String] The encrypted data (formatted as: nonce + auth_tag + cipher_text)
45
+ # @param nonce_length [Integer] The length of the nonce/IV used during encryption
46
+ # @param aad [String] Additional authenticated data (must match the value used during encryption)
47
+ #
48
+ # @return [String] The decrypted plain text
49
+ #
50
+ # @raise [ArgumentError] If the algorithm is not :aes-256-gcm
51
+ # @raise [OpenSSL::Cipher::CipherError] If authentication fails or decryption fails
52
+ #
53
+ # @example
54
+ # key = OpenSSL::Random.random_bytes(32)
55
+ # encrypted = Ecies.sym_encrypt(:"aes-256-gcm", key, "Hello World", 12)
56
+ # decrypted = Ecies.sym_decrypt(:"aes-256-gcm", key, encrypted, 12)
57
+ def sym_decrypt(algorithm, key, cipher_text, nonce_length, aad = "")
58
+ if algorithm != :"aes-256-gcm"
59
+ raise ArgumentError, "Unsupported algorithm: #{algorithm}"
60
+ end
61
+
62
+ nonce = cipher_text[0, nonce_length]
63
+ tag = cipher_text[nonce_length, AEAD_TAG_LENGTH]
64
+ encrypted = cipher_text[nonce_length + AEAD_TAG_LENGTH..]
65
+ decipher = OpenSSL::Cipher.new(algorithm.to_s).decrypt
66
+ decipher.key = key
67
+ decipher.iv_len = nonce_length
68
+ decipher.iv = nonce
69
+ decipher.auth_tag = tag
70
+ decipher.auth_data = aad
71
+ decipher.update(encrypted) + decipher.final
72
+ end
73
+
74
+ module_function :sym_encrypt, :sym_decrypt
75
+ end
data/lib/ecies.rb ADDED
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ecies/config"
4
+ require "ecies/hex"
5
+ require "ecies/hash"
6
+ require "ecies/elliptic"
7
+ require "ecies/symmetric"
8
+
9
+ module Ecies
10
+ COMPRESSED_PUBLIC_KEY_SIZE = 33
11
+ UNCOMPRESSED_PUBLIC_KEY_SIZE = 65
12
+
13
+ # Encrypt with receiver's public key
14
+ #
15
+ # @param [String] receiver_pk The receiver's public key (serialized, raw bytes).
16
+ # @param [String] data The plaintext data to encrypt (raw bytes).
17
+ # @param [Ecies::Config] config The configuration object (optional).
18
+ # @return [String] The encrypted data (ephemeral public key + encrypted data).
19
+ def encrypt(receiver_pk, data, config = DEFAULT_CONFIG)
20
+ ephemeral_sk = generate_key
21
+ raw_ephemeral_sk = decode_hex(ephemeral_sk.send(:serialize))
22
+ ephemeral_pk = ephemeral_sk.pubkey.serialize(compressed: config.is_ephemeral_key_compressed)
23
+ sym_key = encapsulate(raw_ephemeral_sk, receiver_pk, config.is_hkdf_key_compressed)
24
+ encrypted = sym_encrypt(:"aes-256-gcm", sym_key, data, config.symmetric_nonce_length)
25
+ ephemeral_pk + encrypted
26
+ end
27
+
28
+ # Decrypt with receiver's secret key
29
+ #
30
+ # @param [String] receiver_sk The receiver's secret key (serialized, raw bytes).
31
+ # @param [String] data The encrypted data (ephemeral public key + encrypted data).
32
+ # @param [Ecies::Config] config The configuration object (optional).
33
+ # @return [String] The decrypted plaintext data (raw bytes).
34
+ def decrypt(receiver_sk, data, config = DEFAULT_CONFIG)
35
+ pk_size = config.is_ephemeral_key_compressed ?
36
+ COMPRESSED_PUBLIC_KEY_SIZE : UNCOMPRESSED_PUBLIC_KEY_SIZE
37
+ ephemeral_pk = data[0, pk_size]
38
+ encrypted = data[pk_size..]
39
+ sym_key = decapsulate(ephemeral_pk, receiver_sk, config.is_hkdf_key_compressed)
40
+ sym_decrypt(:"aes-256-gcm", sym_key, encrypted, config.symmetric_nonce_length)
41
+ end
42
+
43
+ module_function :encrypt, :decrypt
44
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: eciesrb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Weiliang Li
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: libsecp256k1
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: 0.6.1
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: 0.6.1
26
+ - !ruby/object:Gem::Dependency
27
+ name: openssl
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.3'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.3'
40
+ description: Elliptic Curve Integrated Encryption Scheme for secp256k1 in Ruby, based
41
+ on libsecp256k1 and OpenSSL.
42
+ email: to.be.impressive@gmail.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - CHANGELOG.md
48
+ - LICENSE
49
+ - README.md
50
+ - eciesrb.gemspec
51
+ - lib/ecies.rb
52
+ - lib/ecies/config.rb
53
+ - lib/ecies/elliptic.rb
54
+ - lib/ecies/hash.rb
55
+ - lib/ecies/hex.rb
56
+ - lib/ecies/symmetric.rb
57
+ homepage: https://github.com/ecies/rb
58
+ licenses:
59
+ - MIT
60
+ metadata: {}
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '3.2'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubygems_version: 3.6.9
76
+ specification_version: 4
77
+ summary: Elliptic Curve Integrated Encryption Scheme for secp256k1 in Ruby
78
+ test_files: []