secp256k1rb 0.2.0 → 0.3.0
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 +4 -4
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +14 -0
- data/README.md +5 -1
- data/lib/secp256k1/c.rb +53 -2
- data/lib/secp256k1/ecdh.rb +37 -0
- data/lib/secp256k1/ellswift.rb +20 -0
- data/lib/secp256k1/key.rb +342 -0
- data/lib/secp256k1/musig.rb +38 -0
- data/lib/secp256k1/recovery.rb +23 -0
- data/lib/secp256k1/schnorrsig.rb +57 -3
- data/lib/secp256k1/version.rb +1 -1
- data/lib/secp256k1.rb +84 -0
- metadata +7 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6a26cd2beaa9525ea8f7a0db0e411dbb3d049ec976214f4651e787f02ed76f83
|
|
4
|
+
data.tar.gz: 05c9fce673e0f97e48faa60335cb5de37a3be74a12c50f5cfc541621c11526f2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 71dbb792ccefcf0a852ff5ff96173c2f7c784cff9313489782b03159dd0163860938435d66ec974cccf7fe3755b5bd1ded9bbdf20a77a9d4d41bc2a6b65d63f9
|
|
7
|
+
data.tar.gz: b5e36b1dae73beb4c7e2420efb792c9185fa08f96acbbd2090b5858fc305b88bd9b8a42197900bf1dfea9d1ff9c4b754d491719a63754e817c1c1854969b3db9
|
data/.ruby-gemset
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
secp256k1
|
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ruby-4.0.0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.3.0]
|
|
4
|
+
|
|
5
|
+
- Support secp256k1 v0.7.1.
|
|
6
|
+
- Add FFI bindings for the full v0.7.1 public C API.
|
|
7
|
+
- Add `Secp256k1::Key` module: EC key arithmetic (`tweak_add_seckey`/`tweak_mul_seckey`/`negate_seckey`, `tweak_add_pubkey`/`tweak_mul_pubkey`/`negate_pubkey`, `combine_pubkeys`), x-only public key operations (`xonly_pubkey_from_pubkey`, `xonly_tweak_add_pubkey`, `xonly_tweak_add_check?`) and key pair accessors (`keypair_to_seckey`/`keypair_to_pubkey`/`keypair_to_xonly_pubkey`).
|
|
8
|
+
Also adds `compare_pubkey`/`sort_pubkeys`/`compare_xonly_pubkey` and `keypair_xonly_tweak_add`.
|
|
9
|
+
- Add `Secp256k1::ECDH` module: `ecdh`.
|
|
10
|
+
- Add compact ECDSA signature conversion: `ecdsa_signature_to_compact` / `ecdsa_signature_from_compact`.
|
|
11
|
+
- Add `tagged_sha256` (BIP-340 tagged hash).
|
|
12
|
+
- Add `Secp256k1::EllSwift#ellswift_encode`.
|
|
13
|
+
- Add `Secp256k1::Recover#recoverable_signature_to_ecdsa`.
|
|
14
|
+
- Add `Secp256k1::SchnorrSig#sign_schnorr_custom` / `#verify_schnorr_custom` for variable-length messages.
|
|
15
|
+
- Add `Secp256k1::MuSig#generate_musig_nonce_counter` (deterministic counter-based nonce generation).
|
|
16
|
+
|
|
3
17
|
## [0.1.0] - 2024-05-07
|
|
4
18
|
|
|
5
19
|
- Initial release
|
data/README.md
CHANGED
|
@@ -40,8 +40,11 @@ generate_key_pair
|
|
|
40
40
|
See [here](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1) for available methods.
|
|
41
41
|
In addition, the following modules are also included, so you can use them as they are.
|
|
42
42
|
|
|
43
|
+
* [Key](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/Key)
|
|
44
|
+
* [ECDH](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/ECDH)
|
|
43
45
|
* [Recover](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/Recover)
|
|
44
46
|
* [SchnorrSig](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/SchnorrSig)
|
|
47
|
+
* [MuSig](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/MuSig)
|
|
45
48
|
* [EllSwift](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/EllSwift)
|
|
46
49
|
|
|
47
50
|
### Compatibility
|
|
@@ -49,4 +52,5 @@ In addition, the following modules are also included, so you can use them as the
|
|
|
49
52
|
secp256k1 version | secp256k1rb version
|
|
50
53
|
:---:|:---:
|
|
51
54
|
v0.4.0 | v0.1.x
|
|
52
|
-
|
|
55
|
+
v0.6.0 | v0.2.x
|
|
56
|
+
v0.7.1 | v0.3.x
|
data/lib/secp256k1/c.rb
CHANGED
|
@@ -11,16 +11,42 @@ module Secp256k1
|
|
|
11
11
|
attach_function(:secp256k1_context_create, [:uint], :pointer)
|
|
12
12
|
attach_function(:secp256k1_context_destroy, [:pointer], :void)
|
|
13
13
|
attach_function(:secp256k1_context_randomize, [:pointer, :pointer], :int)
|
|
14
|
+
attach_function(:secp256k1_context_clone, [:pointer], :pointer)
|
|
15
|
+
attach_function(:secp256k1_context_set_error_callback, [:pointer, :pointer, :pointer], :void)
|
|
16
|
+
attach_function(:secp256k1_context_set_illegal_callback, [:pointer, :pointer, :pointer], :void)
|
|
17
|
+
attach_function(:secp256k1_selftest, [], :void)
|
|
18
|
+
# Preallocated context management
|
|
19
|
+
attach_function(:secp256k1_context_preallocated_size, [:uint], :size_t)
|
|
20
|
+
attach_function(:secp256k1_context_preallocated_create, [:pointer, :uint], :pointer)
|
|
21
|
+
attach_function(:secp256k1_context_preallocated_clone_size, [:pointer], :size_t)
|
|
22
|
+
attach_function(:secp256k1_context_preallocated_clone, [:pointer, :pointer], :pointer)
|
|
23
|
+
attach_function(:secp256k1_context_preallocated_destroy, [:pointer], :void)
|
|
14
24
|
attach_function(:secp256k1_ec_pubkey_create, [:pointer, :pointer, :pointer], :int)
|
|
15
25
|
attach_function(:secp256k1_ec_seckey_verify, [:pointer, :pointer], :int)
|
|
26
|
+
# EC key arithmetic (tweak/negate/combine/compare/sort)
|
|
27
|
+
attach_function(:secp256k1_ec_seckey_negate, [:pointer, :pointer], :int)
|
|
28
|
+
attach_function(:secp256k1_ec_seckey_tweak_add, [:pointer, :pointer, :pointer], :int)
|
|
29
|
+
attach_function(:secp256k1_ec_seckey_tweak_mul, [:pointer, :pointer, :pointer], :int)
|
|
30
|
+
attach_function(:secp256k1_ec_pubkey_negate, [:pointer, :pointer], :int)
|
|
31
|
+
attach_function(:secp256k1_ec_pubkey_tweak_add, [:pointer, :pointer, :pointer], :int)
|
|
32
|
+
attach_function(:secp256k1_ec_pubkey_tweak_mul, [:pointer, :pointer, :pointer], :int)
|
|
33
|
+
attach_function(:secp256k1_ec_pubkey_combine, [:pointer, :pointer, :pointer, :size_t], :int)
|
|
34
|
+
attach_function(:secp256k1_ec_pubkey_cmp, [:pointer, :pointer, :pointer], :int)
|
|
35
|
+
attach_function(:secp256k1_ec_pubkey_sort, [:pointer, :pointer, :size_t], :int)
|
|
36
|
+
# Utilities
|
|
37
|
+
attach_function(:secp256k1_tagged_sha256, [:pointer, :pointer, :pointer, :size_t, :pointer, :size_t], :int)
|
|
16
38
|
attach_function(:secp256k1_ecdsa_sign, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
|
|
17
39
|
attach_function(:secp256k1_ec_pubkey_serialize, [:pointer, :pointer, :pointer, :pointer, :uint], :int)
|
|
18
40
|
attach_function(:secp256k1_ecdsa_signature_serialize_der, [:pointer, :pointer, :pointer, :pointer], :int)
|
|
41
|
+
attach_function(:secp256k1_ecdsa_signature_serialize_compact, [:pointer, :pointer, :pointer], :int)
|
|
19
42
|
attach_function(:secp256k1_ec_pubkey_parse, [:pointer, :pointer, :pointer, :size_t], :int)
|
|
20
43
|
attach_function(:secp256k1_ecdsa_signature_parse_der, [:pointer, :pointer, :pointer, :size_t], :int)
|
|
44
|
+
attach_function(:secp256k1_ecdsa_signature_parse_compact, [:pointer, :pointer, :pointer], :int)
|
|
21
45
|
attach_function(:secp256k1_ecdsa_signature_normalize, [:pointer, :pointer, :pointer], :int)
|
|
22
46
|
attach_function(:secp256k1_ecdsa_verify, [:pointer, :pointer, :pointer, :pointer], :int)
|
|
23
47
|
attach_function(:secp256k1_schnorrsig_sign32, [:pointer, :pointer, :pointer, :pointer, :pointer], :int)
|
|
48
|
+
attach_function(:secp256k1_schnorrsig_sign, [:pointer, :pointer, :pointer, :pointer, :pointer], :int)
|
|
49
|
+
attach_function(:secp256k1_schnorrsig_sign_custom, [:pointer, :pointer, :pointer, :size_t, :pointer, :pointer], :int)
|
|
24
50
|
attach_function(:secp256k1_schnorrsig_verify, [:pointer, :pointer, :pointer, :size_t, :pointer], :int)
|
|
25
51
|
attach_function(:secp256k1_keypair_create, [:pointer, :pointer, :pointer], :int)
|
|
26
52
|
attach_function(:secp256k1_xonly_pubkey_parse, [:pointer, :pointer, :pointer], :int)
|
|
@@ -28,21 +54,34 @@ module Secp256k1
|
|
|
28
54
|
attach_function(:secp256k1_ecdsa_recoverable_signature_serialize_compact, [:pointer, :pointer, :pointer, :pointer], :int)
|
|
29
55
|
attach_function(:secp256k1_ecdsa_recover, [:pointer, :pointer, :pointer, :pointer], :int)
|
|
30
56
|
attach_function(:secp256k1_ecdsa_recoverable_signature_parse_compact, [:pointer, :pointer, :pointer, :int], :int)
|
|
57
|
+
attach_function(:secp256k1_ecdsa_recoverable_signature_convert, [:pointer, :pointer, :pointer], :int)
|
|
58
|
+
# for ECDH module
|
|
59
|
+
attach_function(:secp256k1_ecdh, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
|
|
60
|
+
attach_variable(:secp256k1_ecdh_hash_function_sha256, :pointer)
|
|
61
|
+
attach_variable(:secp256k1_ecdh_hash_function_default, :pointer)
|
|
31
62
|
# for EllSwift module
|
|
32
63
|
attach_function(:secp256k1_ellswift_decode, [:pointer, :pointer, :pointer], :int)
|
|
33
64
|
attach_function(:secp256k1_ellswift_create, [:pointer, :pointer, :pointer, :pointer], :int)
|
|
65
|
+
attach_function(:secp256k1_ellswift_encode, [:pointer, :pointer, :pointer, :pointer], :int)
|
|
34
66
|
attach_variable(:secp256k1_ellswift_xdh_hash_function_bip324, :pointer)
|
|
35
67
|
attach_function(:secp256k1_ellswift_xdh, [:pointer, :pointer, :pointer, :pointer, :pointer, :int, :pointer, :pointer], :int)
|
|
36
68
|
# for ExtraKeys module
|
|
37
|
-
attach_function(:secp256k1_xonly_pubkey_parse, [:pointer, :pointer, :pointer], :int)
|
|
38
69
|
attach_function(:secp256k1_xonly_pubkey_serialize, [:pointer, :pointer, :pointer], :int)
|
|
39
|
-
attach_function(:
|
|
70
|
+
attach_function(:secp256k1_xonly_pubkey_cmp, [:pointer, :pointer, :pointer], :int)
|
|
71
|
+
attach_function(:secp256k1_xonly_pubkey_from_pubkey, [:pointer, :pointer, :pointer, :pointer], :int)
|
|
72
|
+
attach_function(:secp256k1_xonly_pubkey_tweak_add, [:pointer, :pointer, :pointer, :pointer], :int)
|
|
73
|
+
attach_function(:secp256k1_xonly_pubkey_tweak_add_check, [:pointer, :pointer, :int, :pointer, :pointer], :int)
|
|
74
|
+
attach_function(:secp256k1_keypair_pub, [:pointer, :pointer, :pointer], :int)
|
|
75
|
+
attach_function(:secp256k1_keypair_sec, [:pointer, :pointer, :pointer], :int)
|
|
76
|
+
attach_function(:secp256k1_keypair_xonly_pub, [:pointer, :pointer, :pointer, :pointer], :int)
|
|
77
|
+
attach_function(:secp256k1_keypair_xonly_tweak_add, [:pointer, :pointer, :pointer], :int)
|
|
40
78
|
# for MuSig module
|
|
41
79
|
attach_function(:secp256k1_musig_pubkey_agg, [:pointer, :pointer, :pointer, :pointer, :size_t], :int)
|
|
42
80
|
attach_function(:secp256k1_musig_pubkey_get, [:pointer, :pointer, :pointer], :int)
|
|
43
81
|
attach_function(:secp256k1_musig_pubkey_ec_tweak_add, [:pointer, :pointer, :pointer, :pointer], :int)
|
|
44
82
|
attach_function(:secp256k1_musig_pubkey_xonly_tweak_add, [:pointer, :pointer, :pointer, :pointer], :int)
|
|
45
83
|
attach_function(:secp256k1_musig_nonce_gen, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
|
|
84
|
+
attach_function(:secp256k1_musig_nonce_gen_counter, [:pointer, :pointer, :pointer, :uint64, :pointer, :pointer, :pointer, :pointer], :int)
|
|
46
85
|
attach_function(:secp256k1_musig_pubnonce_parse, [:pointer, :pointer, :pointer], :int)
|
|
47
86
|
attach_function(:secp256k1_musig_pubnonce_serialize, [:pointer, :pointer, :pointer], :int)
|
|
48
87
|
attach_function(:secp256k1_musig_aggnonce_serialize, [:pointer, :pointer, :pointer], :int)
|
|
@@ -60,5 +99,17 @@ module Secp256k1
|
|
|
60
99
|
def self.ellswift_xdh_hash_function_bip324
|
|
61
100
|
FFI::Pointer.new(secp256k1_ellswift_xdh_hash_function_bip324)
|
|
62
101
|
end
|
|
102
|
+
|
|
103
|
+
# Pointer to secp256k1_ecdh_hash_function_sha256 constant (the default ECDH hash function).
|
|
104
|
+
# @return [FFI::Pointer]
|
|
105
|
+
def self.ecdh_hash_function_sha256
|
|
106
|
+
FFI::Pointer.new(secp256k1_ecdh_hash_function_sha256)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Pointer to secp256k1_ecdh_hash_function_default constant.
|
|
110
|
+
# @return [FFI::Pointer]
|
|
111
|
+
def self.ecdh_hash_function_default
|
|
112
|
+
FFI::Pointer.new(secp256k1_ecdh_hash_function_default)
|
|
113
|
+
end
|
|
63
114
|
end
|
|
64
115
|
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Secp256k1
|
|
2
|
+
# ECDH module.
|
|
3
|
+
# @example
|
|
4
|
+
# include Secp256k1
|
|
5
|
+
#
|
|
6
|
+
# alice_sk, alice_pk = generate_key_pair
|
|
7
|
+
# bob_sk, bob_pk = generate_key_pair
|
|
8
|
+
#
|
|
9
|
+
# # Both parties compute the same shared secret.
|
|
10
|
+
# ecdh(bob_pk, alice_sk) == ecdh(alice_pk, bob_sk)
|
|
11
|
+
#
|
|
12
|
+
module ECDH
|
|
13
|
+
|
|
14
|
+
# Compute an EC Diffie-Hellman shared secret.
|
|
15
|
+
# @param [String] pubkey the other party's public key with hex format.
|
|
16
|
+
# @param [String] private_key your private key with hex format.
|
|
17
|
+
# @param [FFI::Pointer] hash_function (Optional)A pointer to the hash function to use. If omitted, the
|
|
18
|
+
# library default(SHA256 of the compressed point) is used. See {Secp256k1::C.ecdh_hash_function_sha256}.
|
|
19
|
+
# @return [String] 32-byte shared secret with hex format.
|
|
20
|
+
# @raise [Secp256k1::Error] If the secret was invalid or the public key could not be parsed.
|
|
21
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
22
|
+
def ecdh(pubkey, private_key, hash_function: nil)
|
|
23
|
+
raise ArgumentError, "pubkey must be String." unless pubkey.is_a?(String)
|
|
24
|
+
validate_string!("private_key", private_key, 32)
|
|
25
|
+
pubkey = hex2bin(pubkey)
|
|
26
|
+
private_key = hex2bin(private_key)
|
|
27
|
+
with_context do |context|
|
|
28
|
+
internal = parse_pubkey_internal(context, pubkey)
|
|
29
|
+
seckey = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, private_key)
|
|
30
|
+
output = FFI::MemoryPointer.new(:uchar, 32)
|
|
31
|
+
hashfp = hash_function || FFI::Pointer::NULL
|
|
32
|
+
raise Error, 'secp256k1_ecdh failed.' unless secp256k1_ecdh(context, output, internal, seckey, hashfp, nil) == 1
|
|
33
|
+
output.read_string(32).unpack1('H*')
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/lib/secp256k1/ellswift.rb
CHANGED
|
@@ -19,6 +19,26 @@ module Secp256k1
|
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
# Encode a public key into an ElligatorSwift public key.
|
|
23
|
+
# @param [String] pubkey public key with hex format.
|
|
24
|
+
# @param [String] rnd (Optional)32-byte randomness with binary format. If omitted, random bytes are used.
|
|
25
|
+
# @return [String] ElligatorSwift public key with hex format(64 bytes).
|
|
26
|
+
# @raise [Secp256k1::Error] If encoding failed.
|
|
27
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
28
|
+
def ellswift_encode(pubkey, rnd = nil)
|
|
29
|
+
raise ArgumentError, "pubkey must be String." unless pubkey.is_a?(String)
|
|
30
|
+
validate_string!("rnd", rnd, 32) if rnd
|
|
31
|
+
pubkey = hex2bin(pubkey)
|
|
32
|
+
rnd = rnd ? hex2bin(rnd) : SecureRandom.random_bytes(32)
|
|
33
|
+
with_context do |context|
|
|
34
|
+
internal = parse_pubkey_internal(context, pubkey)
|
|
35
|
+
ell64 = FFI::MemoryPointer.new(:uchar, 64)
|
|
36
|
+
rnd32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, rnd)
|
|
37
|
+
raise Error, 'Failed to encode ElligatorSwift public key.' unless secp256k1_ellswift_encode(context, ell64, internal, rnd32) == 1
|
|
38
|
+
ell64.read_string(64).unpack1('H*')
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
22
42
|
# Compute an ElligatorSwift public key for a secret key.
|
|
23
43
|
# @param [String] private_key private key with hex format
|
|
24
44
|
# @return [String] ElligatorSwift public key with hex format.
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
module Secp256k1
|
|
2
|
+
# Key module provides EC key arithmetic (tweak/negate/combine), x-only public key
|
|
3
|
+
# operations used by Taproot, and key pair accessors.
|
|
4
|
+
# @example
|
|
5
|
+
# include Secp256k1
|
|
6
|
+
#
|
|
7
|
+
# sk, pk = generate_key_pair
|
|
8
|
+
# tweak = SecureRandom.bytes(32)
|
|
9
|
+
#
|
|
10
|
+
# # Tweak a private/public key.
|
|
11
|
+
# tweaked_sk = tweak_add_seckey(sk, tweak)
|
|
12
|
+
# tweaked_pk = tweak_add_pubkey(pk, tweak)
|
|
13
|
+
#
|
|
14
|
+
module Key
|
|
15
|
+
|
|
16
|
+
# Negate a private key in place and return the result.
|
|
17
|
+
# @param [String] private_key a private key with hex format.
|
|
18
|
+
# @return [String] negated private key with hex format.
|
|
19
|
+
# @raise [Secp256k1::Error] If the private key is invalid.
|
|
20
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
21
|
+
def negate_seckey(private_key)
|
|
22
|
+
validate_string!("private_key", private_key, 32)
|
|
23
|
+
private_key = hex2bin(private_key)
|
|
24
|
+
with_context do |context|
|
|
25
|
+
seckey = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, private_key)
|
|
26
|
+
raise Error, 'secp256k1_ec_seckey_negate failed.' unless secp256k1_ec_seckey_negate(context, seckey) == 1
|
|
27
|
+
seckey.read_string(32).unpack1('H*')
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Tweak a private key by adding +tweak+ to it.
|
|
32
|
+
# @param [String] private_key a private key with hex format.
|
|
33
|
+
# @param [String] tweak a 32-byte tweak with hex format.
|
|
34
|
+
# @return [String] tweaked private key with hex format.
|
|
35
|
+
# @raise [Secp256k1::Error] If the arguments are invalid or the result is the zero key.
|
|
36
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
37
|
+
def tweak_add_seckey(private_key, tweak)
|
|
38
|
+
tweak_seckey(private_key, tweak, :secp256k1_ec_seckey_tweak_add)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Tweak a private key by multiplying it by +tweak+.
|
|
42
|
+
# @param [String] private_key a private key with hex format.
|
|
43
|
+
# @param [String] tweak a 32-byte tweak with hex format.
|
|
44
|
+
# @return [String] tweaked private key with hex format.
|
|
45
|
+
# @raise [Secp256k1::Error] If the arguments are invalid.
|
|
46
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
47
|
+
def tweak_mul_seckey(private_key, tweak)
|
|
48
|
+
tweak_seckey(private_key, tweak, :secp256k1_ec_seckey_tweak_mul)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Negate a public key.
|
|
52
|
+
# @param [String] pubkey a public key with hex format.
|
|
53
|
+
# @param [Boolean] compressed Whether to return a compressed public key.
|
|
54
|
+
# @return [String] negated public key with hex format.
|
|
55
|
+
# @raise [Secp256k1::Error] If the public key is invalid.
|
|
56
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
57
|
+
def negate_pubkey(pubkey, compressed: true)
|
|
58
|
+
raise ArgumentError, "pubkey must be String." unless pubkey.is_a?(String)
|
|
59
|
+
pubkey = hex2bin(pubkey)
|
|
60
|
+
with_context do |context|
|
|
61
|
+
internal = parse_pubkey_internal(context, pubkey)
|
|
62
|
+
raise Error, 'secp256k1_ec_pubkey_negate failed.' unless secp256k1_ec_pubkey_negate(context, internal) == 1
|
|
63
|
+
serialize_pubkey_internal(context, internal, compressed)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Tweak a public key by adding +tweak+ * G to it.
|
|
68
|
+
# @param [String] pubkey a public key with hex format.
|
|
69
|
+
# @param [String] tweak a 32-byte tweak with hex format.
|
|
70
|
+
# @param [Boolean] compressed Whether to return a compressed public key.
|
|
71
|
+
# @return [String] tweaked public key with hex format.
|
|
72
|
+
# @raise [Secp256k1::Error] If the arguments are invalid or the result is the point at infinity.
|
|
73
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
74
|
+
def tweak_add_pubkey(pubkey, tweak, compressed: true)
|
|
75
|
+
tweak_pubkey(pubkey, tweak, :secp256k1_ec_pubkey_tweak_add, compressed: compressed)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Tweak a public key by multiplying it by +tweak+.
|
|
79
|
+
# @param [String] pubkey a public key with hex format.
|
|
80
|
+
# @param [String] tweak a 32-byte tweak with hex format.
|
|
81
|
+
# @param [Boolean] compressed Whether to return a compressed public key.
|
|
82
|
+
# @return [String] tweaked public key with hex format.
|
|
83
|
+
# @raise [Secp256k1::Error] If the arguments are invalid.
|
|
84
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
85
|
+
def tweak_mul_pubkey(pubkey, tweak, compressed: true)
|
|
86
|
+
tweak_pubkey(pubkey, tweak, :secp256k1_ec_pubkey_tweak_mul, compressed: compressed)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Add a number of public keys together.
|
|
90
|
+
# @param [Array<String>] pubkeys an array of public keys with hex format.
|
|
91
|
+
# @param [Boolean] compressed Whether to return a compressed public key.
|
|
92
|
+
# @return [String] the sum of the public keys with hex format.
|
|
93
|
+
# @raise [Secp256k1::Error] If the sum is the point at infinity.
|
|
94
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
95
|
+
def combine_pubkeys(pubkeys, compressed: true)
|
|
96
|
+
raise ArgumentError, "pubkeys must be Array." unless pubkeys.is_a?(Array)
|
|
97
|
+
raise ArgumentError, "pubkeys must not be empty." if pubkeys.empty?
|
|
98
|
+
with_context do |context|
|
|
99
|
+
internals = pubkeys.map do |pubkey|
|
|
100
|
+
raise ArgumentError, "pubkey must be String." unless pubkey.is_a?(String)
|
|
101
|
+
parse_pubkey_internal(context, hex2bin(pubkey))
|
|
102
|
+
end
|
|
103
|
+
ins = FFI::MemoryPointer.new(:pointer, internals.size)
|
|
104
|
+
ins.write_array_of_pointer(internals)
|
|
105
|
+
combined = FFI::MemoryPointer.new(:uchar, 64)
|
|
106
|
+
raise Error, 'secp256k1_ec_pubkey_combine failed.' unless secp256k1_ec_pubkey_combine(context, combined, ins, internals.size) == 1
|
|
107
|
+
serialize_pubkey_internal(context, combined, compressed)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Convert a public key into an x-only public key.
|
|
112
|
+
# @param [String] pubkey a public key with hex format.
|
|
113
|
+
# @return [Array(String, Integer)] the x-only public key with hex format(32 bytes) and its parity(0 or 1).
|
|
114
|
+
# @raise [Secp256k1::Error] If the public key is invalid.
|
|
115
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
116
|
+
def xonly_pubkey_from_pubkey(pubkey)
|
|
117
|
+
raise ArgumentError, "pubkey must be String." unless pubkey.is_a?(String)
|
|
118
|
+
pubkey = hex2bin(pubkey)
|
|
119
|
+
with_context do |context|
|
|
120
|
+
internal = parse_pubkey_internal(context, pubkey)
|
|
121
|
+
xonly = FFI::MemoryPointer.new(:uchar, 64)
|
|
122
|
+
parity = FFI::MemoryPointer.new(:int)
|
|
123
|
+
raise Error, 'secp256k1_xonly_pubkey_from_pubkey failed.' unless secp256k1_xonly_pubkey_from_pubkey(context, xonly, parity, internal) == 1
|
|
124
|
+
[serialize_xonly_pubkey_internal(context, xonly), parity.read_int]
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Tweak an x-only public key by adding +tweak+ * G to it (used for Taproot output key derivation).
|
|
129
|
+
# @param [String] xonly_pubkey an x-only public key with hex format(32 bytes).
|
|
130
|
+
# @param [String] tweak a 32-byte tweak with hex format.
|
|
131
|
+
# @param [Boolean] compressed Whether to return a compressed public key.
|
|
132
|
+
# @return [Array(String, Integer)] the tweaked(full) public key with hex format and the parity(0 or 1) of the tweaked key.
|
|
133
|
+
# @raise [Secp256k1::Error] If the arguments are invalid.
|
|
134
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
135
|
+
def xonly_tweak_add_pubkey(xonly_pubkey, tweak, compressed: true)
|
|
136
|
+
validate_string!("xonly_pubkey", xonly_pubkey, X_ONLY_PUBKEY_SIZE)
|
|
137
|
+
validate_string!("tweak", tweak, 32)
|
|
138
|
+
xonly_pubkey = hex2bin(xonly_pubkey)
|
|
139
|
+
tweak = hex2bin(tweak)
|
|
140
|
+
with_context do |context|
|
|
141
|
+
xonly = FFI::MemoryPointer.new(:uchar, X_ONLY_PUBKEY_SIZE).put_bytes(0, xonly_pubkey)
|
|
142
|
+
internal = FFI::MemoryPointer.new(:uchar, 64)
|
|
143
|
+
raise Error, 'An invalid x-only public key was specified.' unless secp256k1_xonly_pubkey_parse(context, internal, xonly) == 1
|
|
144
|
+
tweak_ptr = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, tweak)
|
|
145
|
+
output = FFI::MemoryPointer.new(:uchar, 64)
|
|
146
|
+
raise Error, 'secp256k1_xonly_pubkey_tweak_add failed.' unless secp256k1_xonly_pubkey_tweak_add(context, output, internal, tweak_ptr) == 1
|
|
147
|
+
_, parity = xonly_pubkey_from_internal(context, output)
|
|
148
|
+
[serialize_pubkey_internal(context, output, compressed), parity]
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Check that a tweaked x-only public key was computed by tweaking +internal_pubkey+ with +tweak+.
|
|
153
|
+
# @param [String] tweaked_pubkey32 the tweaked x-only public key with hex format(32 bytes).
|
|
154
|
+
# @param [Integer] tweaked_pk_parity the parity(0 or 1) of the tweaked public key.
|
|
155
|
+
# @param [String] internal_pubkey the internal x-only public key with hex format(32 bytes).
|
|
156
|
+
# @param [String] tweak a 32-byte tweak with hex format.
|
|
157
|
+
# @return [Boolean] verification result.
|
|
158
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
159
|
+
def xonly_tweak_add_check?(tweaked_pubkey32, tweaked_pk_parity, internal_pubkey, tweak)
|
|
160
|
+
validate_string!("tweaked_pubkey32", tweaked_pubkey32, X_ONLY_PUBKEY_SIZE)
|
|
161
|
+
validate_string!("internal_pubkey", internal_pubkey, X_ONLY_PUBKEY_SIZE)
|
|
162
|
+
validate_string!("tweak", tweak, 32)
|
|
163
|
+
raise ArgumentError, "tweaked_pk_parity must be 0 or 1." unless [0, 1].include?(tweaked_pk_parity)
|
|
164
|
+
tweaked_pubkey32 = hex2bin(tweaked_pubkey32)
|
|
165
|
+
internal_pubkey = hex2bin(internal_pubkey)
|
|
166
|
+
tweak = hex2bin(tweak)
|
|
167
|
+
with_context do |context|
|
|
168
|
+
internal = FFI::MemoryPointer.new(:uchar, X_ONLY_PUBKEY_SIZE).put_bytes(0, internal_pubkey)
|
|
169
|
+
internal_xonly = FFI::MemoryPointer.new(:uchar, 64)
|
|
170
|
+
return false unless secp256k1_xonly_pubkey_parse(context, internal_xonly, internal) == 1
|
|
171
|
+
tweaked_ptr = FFI::MemoryPointer.new(:uchar, X_ONLY_PUBKEY_SIZE).put_bytes(0, tweaked_pubkey32)
|
|
172
|
+
tweak_ptr = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, tweak)
|
|
173
|
+
secp256k1_xonly_pubkey_tweak_add_check(context, tweaked_ptr, tweaked_pk_parity, internal_xonly, tweak_ptr) == 1
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Get the public key from a key pair.
|
|
178
|
+
# @param [String] keypair a key pair with hex format(96 bytes), created by {#create_keypair}.
|
|
179
|
+
# @param [Boolean] compressed Whether to return a compressed public key.
|
|
180
|
+
# @return [String] public key with hex format.
|
|
181
|
+
# @raise [Secp256k1::Error] If the key pair is invalid.
|
|
182
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
183
|
+
def keypair_to_pubkey(keypair, compressed: true)
|
|
184
|
+
validate_string!("keypair", keypair, 96)
|
|
185
|
+
keypair = hex2bin(keypair)
|
|
186
|
+
with_context do |context|
|
|
187
|
+
keypair_ptr = FFI::MemoryPointer.new(:uchar, 96).put_bytes(0, keypair)
|
|
188
|
+
internal = FFI::MemoryPointer.new(:uchar, 64)
|
|
189
|
+
raise Error, 'secp256k1_keypair_pub failed.' unless secp256k1_keypair_pub(context, internal, keypair_ptr) == 1
|
|
190
|
+
serialize_pubkey_internal(context, internal, compressed)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Get the private key from a key pair.
|
|
195
|
+
# @param [String] keypair a key pair with hex format(96 bytes), created by {#create_keypair}.
|
|
196
|
+
# @return [String] private key with hex format.
|
|
197
|
+
# @raise [Secp256k1::Error] If the key pair is invalid.
|
|
198
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
199
|
+
def keypair_to_seckey(keypair)
|
|
200
|
+
validate_string!("keypair", keypair, 96)
|
|
201
|
+
keypair = hex2bin(keypair)
|
|
202
|
+
with_context do |context|
|
|
203
|
+
keypair_ptr = FFI::MemoryPointer.new(:uchar, 96).put_bytes(0, keypair)
|
|
204
|
+
seckey = FFI::MemoryPointer.new(:uchar, 32)
|
|
205
|
+
raise Error, 'secp256k1_keypair_sec failed.' unless secp256k1_keypair_sec(context, seckey, keypair_ptr) == 1
|
|
206
|
+
seckey.read_string(32).unpack1('H*')
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Get the x-only public key from a key pair.
|
|
211
|
+
# @param [String] keypair a key pair with hex format(96 bytes), created by {#create_keypair}.
|
|
212
|
+
# @return [Array(String, Integer)] the x-only public key with hex format(32 bytes) and its parity(0 or 1).
|
|
213
|
+
# @raise [Secp256k1::Error] If the key pair is invalid.
|
|
214
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
215
|
+
def keypair_to_xonly_pubkey(keypair)
|
|
216
|
+
validate_string!("keypair", keypair, 96)
|
|
217
|
+
keypair = hex2bin(keypair)
|
|
218
|
+
with_context do |context|
|
|
219
|
+
keypair_ptr = FFI::MemoryPointer.new(:uchar, 96).put_bytes(0, keypair)
|
|
220
|
+
xonly = FFI::MemoryPointer.new(:uchar, 64)
|
|
221
|
+
parity = FFI::MemoryPointer.new(:int)
|
|
222
|
+
raise Error, 'secp256k1_keypair_xonly_pub failed.' unless secp256k1_keypair_xonly_pub(context, xonly, parity, keypair_ptr) == 1
|
|
223
|
+
[serialize_xonly_pubkey_internal(context, xonly), parity.read_int]
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Compare two public keys using lexicographic(of compressed serialization) order.
|
|
228
|
+
# @param [String] pubkey1 a public key with hex format.
|
|
229
|
+
# @param [String] pubkey2 a public key with hex format.
|
|
230
|
+
# @return [Integer] negative if pubkey1 < pubkey2, 0 if equal, positive if pubkey1 > pubkey2.
|
|
231
|
+
# @raise [Secp256k1::Error] If a public key is invalid.
|
|
232
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
233
|
+
def compare_pubkey(pubkey1, pubkey2)
|
|
234
|
+
raise ArgumentError, "pubkey1 must be String." unless pubkey1.is_a?(String)
|
|
235
|
+
raise ArgumentError, "pubkey2 must be String." unless pubkey2.is_a?(String)
|
|
236
|
+
with_context do |context|
|
|
237
|
+
a = parse_pubkey_internal(context, hex2bin(pubkey1))
|
|
238
|
+
b = parse_pubkey_internal(context, hex2bin(pubkey2))
|
|
239
|
+
secp256k1_ec_pubkey_cmp(context, a, b)
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Sort public keys using lexicographic(of compressed serialization) order.
|
|
244
|
+
# @param [Array<String>] pubkeys an array of public keys with hex format.
|
|
245
|
+
# @param [Boolean] compressed Whether to return compressed public keys.
|
|
246
|
+
# @return [Array<String>] the sorted public keys with hex format.
|
|
247
|
+
# @raise [Secp256k1::Error] If a public key is invalid.
|
|
248
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
249
|
+
def sort_pubkeys(pubkeys, compressed: true)
|
|
250
|
+
raise ArgumentError, "pubkeys must be Array." unless pubkeys.is_a?(Array)
|
|
251
|
+
raise ArgumentError, "pubkeys must not be empty." if pubkeys.empty?
|
|
252
|
+
with_context do |context|
|
|
253
|
+
internals = pubkeys.map do |pubkey|
|
|
254
|
+
raise ArgumentError, "pubkey must be String." unless pubkey.is_a?(String)
|
|
255
|
+
parse_pubkey_internal(context, hex2bin(pubkey))
|
|
256
|
+
end
|
|
257
|
+
arr = FFI::MemoryPointer.new(:pointer, internals.size)
|
|
258
|
+
arr.write_array_of_pointer(internals)
|
|
259
|
+
raise Error, 'secp256k1_ec_pubkey_sort failed.' unless secp256k1_ec_pubkey_sort(context, arr, internals.size) == 1
|
|
260
|
+
arr.read_array_of_pointer(internals.size).map { |ptr| serialize_pubkey_internal(context, ptr, compressed) }
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Compare two x-only public keys using lexicographic order.
|
|
265
|
+
# @param [String] pubkey1 an x-only public key with hex format(32 bytes).
|
|
266
|
+
# @param [String] pubkey2 an x-only public key with hex format(32 bytes).
|
|
267
|
+
# @return [Integer] negative if pubkey1 < pubkey2, 0 if equal, positive if pubkey1 > pubkey2.
|
|
268
|
+
# @raise [Secp256k1::Error] If a public key is invalid.
|
|
269
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
270
|
+
def compare_xonly_pubkey(pubkey1, pubkey2)
|
|
271
|
+
validate_string!("pubkey1", pubkey1, X_ONLY_PUBKEY_SIZE)
|
|
272
|
+
validate_string!("pubkey2", pubkey2, X_ONLY_PUBKEY_SIZE)
|
|
273
|
+
with_context do |context|
|
|
274
|
+
a = parse_xonly_pubkey_internal(context, hex2bin(pubkey1))
|
|
275
|
+
b = parse_xonly_pubkey_internal(context, hex2bin(pubkey2))
|
|
276
|
+
secp256k1_xonly_pubkey_cmp(context, a, b)
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Tweak a key pair by adding +tweak+ to the key pair's x-only context.
|
|
281
|
+
# @param [String] keypair a key pair with hex format(96 bytes), created by {#create_keypair}.
|
|
282
|
+
# @param [String] tweak a 32-byte tweak with hex format.
|
|
283
|
+
# @return [String] the tweaked key pair with hex format(96 bytes).
|
|
284
|
+
# @raise [Secp256k1::Error] If the arguments are invalid.
|
|
285
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
286
|
+
def keypair_xonly_tweak_add(keypair, tweak)
|
|
287
|
+
validate_string!("keypair", keypair, 96)
|
|
288
|
+
validate_string!("tweak", tweak, 32)
|
|
289
|
+
keypair = hex2bin(keypair)
|
|
290
|
+
tweak = hex2bin(tweak)
|
|
291
|
+
with_context do |context|
|
|
292
|
+
keypair_ptr = FFI::MemoryPointer.new(:uchar, 96).put_bytes(0, keypair)
|
|
293
|
+
tweak_ptr = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, tweak)
|
|
294
|
+
raise Error, 'secp256k1_keypair_xonly_tweak_add failed.' unless secp256k1_keypair_xonly_tweak_add(context, keypair_ptr, tweak_ptr) == 1
|
|
295
|
+
keypair_ptr.read_string(96).unpack1('H*')
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
private
|
|
300
|
+
|
|
301
|
+
def parse_xonly_pubkey_internal(context, xonly_pubkey)
|
|
302
|
+
xonly = FFI::MemoryPointer.new(:uchar, X_ONLY_PUBKEY_SIZE).put_bytes(0, xonly_pubkey)
|
|
303
|
+
internal = FFI::MemoryPointer.new(:uchar, 64)
|
|
304
|
+
raise Error, 'An invalid x-only public key was specified.' unless secp256k1_xonly_pubkey_parse(context, internal, xonly) == 1
|
|
305
|
+
internal
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def tweak_seckey(private_key, tweak, func)
|
|
309
|
+
validate_string!("private_key", private_key, 32)
|
|
310
|
+
validate_string!("tweak", tweak, 32)
|
|
311
|
+
private_key = hex2bin(private_key)
|
|
312
|
+
tweak = hex2bin(tweak)
|
|
313
|
+
with_context do |context|
|
|
314
|
+
seckey = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, private_key)
|
|
315
|
+
tweak_ptr = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, tweak)
|
|
316
|
+
raise Error, "#{func} failed." unless send(func, context, seckey, tweak_ptr) == 1
|
|
317
|
+
seckey.read_string(32).unpack1('H*')
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def tweak_pubkey(pubkey, tweak, func, compressed: true)
|
|
322
|
+
raise ArgumentError, "pubkey must be String." unless pubkey.is_a?(String)
|
|
323
|
+
validate_string!("tweak", tweak, 32)
|
|
324
|
+
pubkey = hex2bin(pubkey)
|
|
325
|
+
tweak = hex2bin(tweak)
|
|
326
|
+
with_context do |context|
|
|
327
|
+
internal = parse_pubkey_internal(context, pubkey)
|
|
328
|
+
tweak_ptr = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, tweak)
|
|
329
|
+
raise Error, "#{func} failed." unless send(func, context, internal, tweak_ptr) == 1
|
|
330
|
+
serialize_pubkey_internal(context, internal, compressed)
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Get x-only pubkey and parity from an internal(64 bytes) full pubkey pointer.
|
|
335
|
+
def xonly_pubkey_from_internal(context, internal_pubkey)
|
|
336
|
+
xonly = FFI::MemoryPointer.new(:uchar, 64)
|
|
337
|
+
parity = FFI::MemoryPointer.new(:int)
|
|
338
|
+
raise Error, 'secp256k1_xonly_pubkey_from_pubkey failed.' unless secp256k1_xonly_pubkey_from_pubkey(context, xonly, parity, internal_pubkey) == 1
|
|
339
|
+
[serialize_xonly_pubkey_internal(context, xonly), parity.read_int]
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
end
|
data/lib/secp256k1/musig.rb
CHANGED
|
@@ -132,6 +132,44 @@ module Secp256k1
|
|
|
132
132
|
end
|
|
133
133
|
end
|
|
134
134
|
|
|
135
|
+
# Generate a nonce pair deterministically using a non-repeating counter instead of session randomness.
|
|
136
|
+
# The caller must ensure that +counter+ never repeats for the same key pair.
|
|
137
|
+
# @param [Integer] counter A non-repeating counter(uint64).
|
|
138
|
+
# @param [String] private_key The private key for which the partial signature is generated, with hex format.
|
|
139
|
+
# @param [Secp256k1::MuSig::KeyAggContext] key_agg_ctx (Optional) The aggregated public key context.
|
|
140
|
+
# @param [String] msg (Optional) The message to be signed.
|
|
141
|
+
# @param [String] extra_in (Optional) The auxiliary input.
|
|
142
|
+
# @return [Array(String)] The array of secret nonce and public nonce with hex format.
|
|
143
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
144
|
+
# @raise [Secp256k1::Error]
|
|
145
|
+
def generate_musig_nonce_counter(counter, private_key, key_agg_ctx: nil, msg: nil, extra_in: nil)
|
|
146
|
+
raise ArgumentError, "counter must be Integer." unless counter.is_a?(Integer)
|
|
147
|
+
raise ArgumentError, "counter must be between 0 and 2**64-1." if counter < 0 || counter > (2**64 - 1)
|
|
148
|
+
validate_string!("private_key", private_key, 32)
|
|
149
|
+
validate_string!("msg", msg, 32) if msg
|
|
150
|
+
validate_string!("extra_in", extra_in, 32) if extra_in
|
|
151
|
+
if key_agg_ctx
|
|
152
|
+
raise ArgumentError, "key_agg_ctx must be Secp256k1::MuSig::KeyAggContext." unless key_agg_ctx.is_a?(KeyAggContext)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
with_context do |context|
|
|
156
|
+
keypair = [create_keypair(private_key)].pack('H*')
|
|
157
|
+
keypair_ptr = FFI::MemoryPointer.new(:uchar, 96).put_bytes(0, keypair)
|
|
158
|
+
pubnonce = FFI::MemoryPointer.new(:uchar, 132)
|
|
159
|
+
secnonce = FFI::MemoryPointer.new(:uchar, 132)
|
|
160
|
+
msg32 = msg ? FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(msg)) : nil
|
|
161
|
+
extra_input32 = extra_in ? FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(extra_in)) : nil
|
|
162
|
+
cache_ptr = key_agg_ctx ? key_agg_ctx.pointer : nil
|
|
163
|
+
|
|
164
|
+
raise Error, "arguments is invalid." unless secp256k1_musig_nonce_gen_counter(
|
|
165
|
+
context, secnonce, pubnonce, counter, keypair_ptr, msg32, cache_ptr, extra_input32) == 1
|
|
166
|
+
|
|
167
|
+
pub66 = FFI::MemoryPointer.new(:uchar, 66)
|
|
168
|
+
secp256k1_musig_pubnonce_serialize(context, pub66, pubnonce)
|
|
169
|
+
[secnonce.read_string(132).unpack1('H*'), pub66.read_string(66).unpack1('H*')]
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
135
173
|
# Aggregates the nonces of all signers into a single nonce.
|
|
136
174
|
# @param [Array] pub_nonces An array of public nonces sent by the signers.
|
|
137
175
|
# @return [String] An aggregated public nonce.
|
data/lib/secp256k1/recovery.rb
CHANGED
|
@@ -68,5 +68,28 @@ module Secp256k1
|
|
|
68
68
|
serialize_pubkey_internal(context, pubkey.read_string(64), compressed)
|
|
69
69
|
end
|
|
70
70
|
end
|
|
71
|
+
|
|
72
|
+
# Convert a recoverable signature into a normal DER-encoded ECDSA signature.
|
|
73
|
+
# @param [String] signature The recoverable signature with binary format(65 bytes), as accepted by {#recover}.
|
|
74
|
+
# @return [String] DER-encoded signature with binary format.
|
|
75
|
+
# @raise [Secp256k1::Error] If conversion failed.
|
|
76
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
77
|
+
def recoverable_signature_to_ecdsa(signature)
|
|
78
|
+
validate_string!("signature", signature, 65)
|
|
79
|
+
signature = hex2bin(signature)
|
|
80
|
+
rec = (signature[0].ord - 0x1b) & 3
|
|
81
|
+
raise ArgumentError, "rec must be between 0 and 3." if rec < 0 || rec > 3
|
|
82
|
+
with_context do |context|
|
|
83
|
+
recoverable = FFI::MemoryPointer.new(:uchar, 65)
|
|
84
|
+
input = FFI::MemoryPointer.new(:uchar, 64).put_bytes(0, signature[1..-1])
|
|
85
|
+
raise Error, 'secp256k1_ecdsa_recoverable_signature_parse_compact failed.' unless secp256k1_ecdsa_recoverable_signature_parse_compact(context, recoverable, input, rec) == 1
|
|
86
|
+
internal_signature = FFI::MemoryPointer.new(:uchar, 64)
|
|
87
|
+
raise Error, 'secp256k1_ecdsa_recoverable_signature_convert failed.' unless secp256k1_ecdsa_recoverable_signature_convert(context, internal_signature, recoverable) == 1
|
|
88
|
+
output = FFI::MemoryPointer.new(:uchar, 72)
|
|
89
|
+
output_len = FFI::MemoryPointer.new(:uint64).put_uint64(0, 72)
|
|
90
|
+
raise Error, 'secp256k1_ecdsa_signature_serialize_der failed.' unless secp256k1_ecdsa_signature_serialize_der(context, output, output_len, internal_signature) == 1
|
|
91
|
+
output.read_string(output_len.read_uint64)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
71
94
|
end
|
|
72
95
|
end
|
data/lib/secp256k1/schnorrsig.rb
CHANGED
|
@@ -36,7 +36,44 @@ module Secp256k1
|
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
#
|
|
39
|
+
# Magic bytes for secp256k1_schnorrsig_extraparams.
|
|
40
|
+
SCHNORRSIG_EXTRAPARAMS_MAGIC = [0xda, 0x6f, 0xb3, 0x8c].pack('C*').freeze
|
|
41
|
+
|
|
42
|
+
# Sign to a variable-length message using schnorr (BIP340 sign with custom parameters).
|
|
43
|
+
# @param [String] data The message being signed with binary format. Unlike {#sign_schnorr}, the length is arbitrary.
|
|
44
|
+
# @param [String] private_key a private key with hex format using sign.
|
|
45
|
+
# @param [String] aux_rand (Optional)The 32-byte extra entropy.
|
|
46
|
+
# @return [String] signature data with binary format(64 bytes).
|
|
47
|
+
# @raise [Secp256k1::Error] If signing failed.
|
|
48
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
49
|
+
def sign_schnorr_custom(data, private_key, aux_rand = nil)
|
|
50
|
+
raise ArgumentError, "data must be String." unless data.is_a?(String)
|
|
51
|
+
validate_string!("private_key", private_key, 32)
|
|
52
|
+
validate_string!("aux_rand", aux_rand, 32) if aux_rand
|
|
53
|
+
private_key = hex2bin(private_key)
|
|
54
|
+
data = hex2bin(data)
|
|
55
|
+
aux_rand = hex2bin(aux_rand) if aux_rand
|
|
56
|
+
|
|
57
|
+
with_context do |context|
|
|
58
|
+
keypair = [create_keypair(private_key)].pack('H*')
|
|
59
|
+
keypair = FFI::MemoryPointer.new(:uchar, 96).put_bytes(0, keypair)
|
|
60
|
+
signature = FFI::MemoryPointer.new(:uchar, 64)
|
|
61
|
+
msg = FFI::MemoryPointer.new(:uchar, [data.bytesize, 1].max).put_bytes(0, data)
|
|
62
|
+
|
|
63
|
+
# Build secp256k1_schnorrsig_extraparams: magic[4], noncefp(NULL=default BIP340), ndata(aux_rand or NULL).
|
|
64
|
+
ptr_size = FFI::Pointer.size
|
|
65
|
+
extraparams = FFI::MemoryPointer.new(:uchar, ptr_size * 3)
|
|
66
|
+
extraparams.put_bytes(0, SCHNORRSIG_EXTRAPARAMS_MAGIC)
|
|
67
|
+
extraparams.put_pointer(ptr_size, FFI::Pointer::NULL)
|
|
68
|
+
ndata = aux_rand ? FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, aux_rand) : FFI::Pointer::NULL
|
|
69
|
+
extraparams.put_pointer(ptr_size * 2, ndata)
|
|
70
|
+
|
|
71
|
+
raise Error, 'Failed to generate schnorr signature.' unless secp256k1_schnorrsig_sign_custom(context, signature, msg, data.bytesize, keypair, extraparams) == 1
|
|
72
|
+
signature.read_string(64)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Verify schnorr signature.
|
|
40
77
|
# @param [String] data The 32-byte message hash assumed to be signed.
|
|
41
78
|
# @param [String] signature signature data with binary format
|
|
42
79
|
# @param [String] pubkey a public key with hex format using verify.
|
|
@@ -44,6 +81,23 @@ module Secp256k1
|
|
|
44
81
|
# @raise [ArgumentError] If invalid arguments specified.
|
|
45
82
|
def verify_schnorr(data, signature, pubkey)
|
|
46
83
|
validate_string!("data", data, 32)
|
|
84
|
+
verify_schnorr_internal(data, signature, pubkey)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Verify a schnorr signature over a variable-length message (counterpart of {#sign_schnorr_custom}).
|
|
88
|
+
# @param [String] data The message assumed to be signed. The length is arbitrary.
|
|
89
|
+
# @param [String] signature signature data with binary format(64 bytes).
|
|
90
|
+
# @param [String] pubkey an x-only public key with hex format(32 bytes) using verify.
|
|
91
|
+
# @return [Boolean] verification result.
|
|
92
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
93
|
+
def verify_schnorr_custom(data, signature, pubkey)
|
|
94
|
+
raise ArgumentError, "data must be String." unless data.is_a?(String)
|
|
95
|
+
verify_schnorr_internal(data, signature, pubkey)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
def verify_schnorr_internal(data, signature, pubkey)
|
|
47
101
|
validate_string!("signature", signature, 64)
|
|
48
102
|
validate_string!("pubkey", pubkey, 32)
|
|
49
103
|
data = hex2bin(data)
|
|
@@ -54,8 +108,8 @@ module Secp256k1
|
|
|
54
108
|
pubkey = [full_pubkey_from_xonly_pubkey(pubkey)].pack('H*')
|
|
55
109
|
xonly_pubkey = FFI::MemoryPointer.new(:uchar, pubkey.bytesize).put_bytes(0, pubkey)
|
|
56
110
|
signature = FFI::MemoryPointer.new(:uchar, signature.bytesize).put_bytes(0, signature)
|
|
57
|
-
|
|
58
|
-
result = secp256k1_schnorrsig_verify(context, signature,
|
|
111
|
+
msg = FFI::MemoryPointer.new(:uchar, [data.bytesize, 1].max).put_bytes(0, data)
|
|
112
|
+
result = secp256k1_schnorrsig_verify(context, signature, msg, data.bytesize, xonly_pubkey)
|
|
59
113
|
result == 1
|
|
60
114
|
end
|
|
61
115
|
end
|
data/lib/secp256k1/version.rb
CHANGED
data/lib/secp256k1.rb
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
require 'securerandom'
|
|
4
4
|
require_relative "secp256k1/version"
|
|
5
5
|
require_relative 'secp256k1/c'
|
|
6
|
+
require_relative 'secp256k1/key'
|
|
7
|
+
require_relative 'secp256k1/ecdh'
|
|
6
8
|
require_relative 'secp256k1/recovery'
|
|
7
9
|
require_relative 'secp256k1/ellswift'
|
|
8
10
|
require_relative 'secp256k1/schnorrsig'
|
|
@@ -28,6 +30,8 @@ module Secp256k1
|
|
|
28
30
|
class Error < StandardError; end
|
|
29
31
|
|
|
30
32
|
include C
|
|
33
|
+
include Key
|
|
34
|
+
include ECDH
|
|
31
35
|
include Recover
|
|
32
36
|
include SchnorrSig
|
|
33
37
|
include EllSwift
|
|
@@ -223,6 +227,64 @@ module Secp256k1
|
|
|
223
227
|
end
|
|
224
228
|
end
|
|
225
229
|
|
|
230
|
+
# Convert a DER-encoded ECDSA signature into compact(64 bytes) format.
|
|
231
|
+
# @param [String] signature DER-encoded signature with binary format.
|
|
232
|
+
# @return [String] compact signature(64 bytes) with binary format.
|
|
233
|
+
# @raise [Secp256k1::Error] If the signature could not be parsed.
|
|
234
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
235
|
+
def ecdsa_signature_to_compact(signature)
|
|
236
|
+
raise ArgumentError, "signature must be String." unless signature.is_a?(String)
|
|
237
|
+
signature = hex2bin(signature)
|
|
238
|
+
with_context do |context|
|
|
239
|
+
sig_ptr = FFI::MemoryPointer.new(:uchar, signature.bytesize).put_bytes(0, signature)
|
|
240
|
+
internal_signature = FFI::MemoryPointer.new(:uchar, 64)
|
|
241
|
+
raise Error, 'secp256k1_ecdsa_signature_parse_der failed.' unless secp256k1_ecdsa_signature_parse_der(context, internal_signature, sig_ptr, signature.bytesize) == 1
|
|
242
|
+
output = FFI::MemoryPointer.new(:uchar, 64)
|
|
243
|
+
secp256k1_ecdsa_signature_serialize_compact(context, output, internal_signature)
|
|
244
|
+
output.read_string(64)
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Convert a compact(64 bytes) ECDSA signature into DER-encoded format.
|
|
249
|
+
# @param [String] signature compact signature(64 bytes) with binary format.
|
|
250
|
+
# @return [String] DER-encoded signature with binary format.
|
|
251
|
+
# @raise [Secp256k1::Error] If the signature could not be parsed.
|
|
252
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
253
|
+
def ecdsa_signature_from_compact(signature)
|
|
254
|
+
validate_string!("signature", signature, 64)
|
|
255
|
+
signature = hex2bin(signature)
|
|
256
|
+
with_context do |context|
|
|
257
|
+
sig_ptr = FFI::MemoryPointer.new(:uchar, 64).put_bytes(0, signature)
|
|
258
|
+
internal_signature = FFI::MemoryPointer.new(:uchar, 64)
|
|
259
|
+
raise Error, 'secp256k1_ecdsa_signature_parse_compact failed.' unless secp256k1_ecdsa_signature_parse_compact(context, internal_signature, sig_ptr) == 1
|
|
260
|
+
output = FFI::MemoryPointer.new(:uchar, 72)
|
|
261
|
+
output_len = FFI::MemoryPointer.new(:uint64).put_uint64(0, 72)
|
|
262
|
+
raise Error, 'secp256k1_ecdsa_signature_serialize_der failed.' unless secp256k1_ecdsa_signature_serialize_der(context, output, output_len, internal_signature) == 1
|
|
263
|
+
output.read_string(output_len.read_uint64)
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Compute a tagged hash as defined in BIP-340: SHA256(SHA256(tag) || SHA256(tag) || msg).
|
|
268
|
+
# @param [String] tag the tag with binary format.
|
|
269
|
+
# @param [String] msg the message with binary format.
|
|
270
|
+
# @return [String] 32-byte tagged hash with hex format.
|
|
271
|
+
# @raise [Secp256k1::Error] If the hash could not be computed.
|
|
272
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
273
|
+
def tagged_sha256(tag, msg)
|
|
274
|
+
raise ArgumentError, "tag must be String." unless tag.is_a?(String)
|
|
275
|
+
raise ArgumentError, "msg must be String." unless msg.is_a?(String)
|
|
276
|
+
tag = hex2bin(tag)
|
|
277
|
+
msg = hex2bin(msg)
|
|
278
|
+
with_context do |context|
|
|
279
|
+
hash32 = FFI::MemoryPointer.new(:uchar, 32)
|
|
280
|
+
# The library requires non-NULL pointers even for zero-length input, so allocate at least 1 byte.
|
|
281
|
+
tag_ptr = FFI::MemoryPointer.new(:uchar, [tag.bytesize, 1].max).put_bytes(0, tag)
|
|
282
|
+
msg_ptr = FFI::MemoryPointer.new(:uchar, [msg.bytesize, 1].max).put_bytes(0, msg)
|
|
283
|
+
raise Error, 'secp256k1_tagged_sha256 failed.' unless secp256k1_tagged_sha256(context, hash32, tag_ptr, tag.bytesize, msg_ptr, msg.bytesize) == 1
|
|
284
|
+
hash32.read_string(32).unpack1('H*')
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
226
288
|
private
|
|
227
289
|
|
|
228
290
|
# Calculate full public key(64 bytes) from public key(32 bytes).
|
|
@@ -239,6 +301,28 @@ module Secp256k1
|
|
|
239
301
|
end
|
|
240
302
|
end
|
|
241
303
|
|
|
304
|
+
# Parse a serialized public key into an internal 64-byte pubkey pointer.
|
|
305
|
+
# @param [FFI::Pointer] context secp256k1 context.
|
|
306
|
+
# @param [String] pubkey serialized public key with binary format.
|
|
307
|
+
# @return [FFI::Pointer] internal pubkey pointer(64 bytes).
|
|
308
|
+
# @raise [Secp256k1::Error] If the public key is invalid.
|
|
309
|
+
def parse_pubkey_internal(context, pubkey)
|
|
310
|
+
pubkey_ptr = FFI::MemoryPointer.new(:uchar, pubkey.bytesize).put_bytes(0, pubkey)
|
|
311
|
+
internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
|
|
312
|
+
raise Error, 'An invalid public key was specified.' unless secp256k1_ec_pubkey_parse(context, internal_pubkey, pubkey_ptr, pubkey.bytesize) == 1
|
|
313
|
+
internal_pubkey
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# Serialize an internal x-only public key pointer(64 bytes) to 32-byte hex.
|
|
317
|
+
# @param [FFI::Pointer] context secp256k1 context.
|
|
318
|
+
# @param [FFI::Pointer] xonly_input internal x-only pubkey pointer(64 bytes).
|
|
319
|
+
# @return [String] x-only public key with hex format(32 bytes).
|
|
320
|
+
def serialize_xonly_pubkey_internal(context, xonly_input)
|
|
321
|
+
output = FFI::MemoryPointer.new(:uchar, X_ONLY_PUBKEY_SIZE)
|
|
322
|
+
secp256k1_xonly_pubkey_serialize(context, output, xonly_input)
|
|
323
|
+
output.read_string(X_ONLY_PUBKEY_SIZE).unpack1('H*')
|
|
324
|
+
end
|
|
325
|
+
|
|
242
326
|
def generate_pubkey_in_context(context, private_key, compressed: true)
|
|
243
327
|
internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
|
|
244
328
|
priv_ptr = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(private_key))
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: secp256k1rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- azuchi
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: ffi
|
|
@@ -32,6 +31,8 @@ extensions: []
|
|
|
32
31
|
extra_rdoc_files: []
|
|
33
32
|
files:
|
|
34
33
|
- ".rspec"
|
|
34
|
+
- ".ruby-gemset"
|
|
35
|
+
- ".ruby-version"
|
|
35
36
|
- CHANGELOG.md
|
|
36
37
|
- CODE_OF_CONDUCT.md
|
|
37
38
|
- Gemfile
|
|
@@ -42,7 +43,9 @@ files:
|
|
|
42
43
|
- bin/setup
|
|
43
44
|
- lib/secp256k1.rb
|
|
44
45
|
- lib/secp256k1/c.rb
|
|
46
|
+
- lib/secp256k1/ecdh.rb
|
|
45
47
|
- lib/secp256k1/ellswift.rb
|
|
48
|
+
- lib/secp256k1/key.rb
|
|
46
49
|
- lib/secp256k1/musig.rb
|
|
47
50
|
- lib/secp256k1/musig/key_agg.rb
|
|
48
51
|
- lib/secp256k1/musig/session.rb
|
|
@@ -58,7 +61,6 @@ metadata:
|
|
|
58
61
|
homepage_uri: https://github.com/azuchi/secp256k1rb
|
|
59
62
|
source_code_uri: https://github.com/azuchi/secp256k1rb
|
|
60
63
|
changelog_uri: https://github.com/azuchi/secp256k1rb
|
|
61
|
-
post_install_message:
|
|
62
64
|
rdoc_options: []
|
|
63
65
|
require_paths:
|
|
64
66
|
- lib
|
|
@@ -73,8 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
73
75
|
- !ruby/object:Gem::Version
|
|
74
76
|
version: '0'
|
|
75
77
|
requirements: []
|
|
76
|
-
rubygems_version: 3.
|
|
77
|
-
signing_key:
|
|
78
|
+
rubygems_version: 3.6.9
|
|
78
79
|
specification_version: 4
|
|
79
80
|
summary: Ruby binding for libsecp256k1.
|
|
80
81
|
test_files: []
|