secp256k1rb 0.3.0 → 0.8.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/CHANGELOG.md +18 -0
- data/Gemfile +1 -0
- data/README.md +11 -0
- data/lib/secp256k1/c.rb +36 -1
- data/lib/secp256k1/silentpayments.rb +319 -0
- data/lib/secp256k1/version.rb +1 -1
- data/lib/secp256k1.rb +8 -3
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ecd7e009765f35dce95f533c357900c4a7c4d6018cbd1f9dd3139af22b4e7c85
|
|
4
|
+
data.tar.gz: 0f6868a1f010cbca12e4ef7b4efe66713ae46fa6a28faf958dc3ee0ec529c8a9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b498f4d026083bd102410e7a8fff7cf2cb59b7f9b0f7033e0bcad9838d9e5fe718f87b9a2c9f8cb2f23439c66a4c612969d6f011fe00c24ce1a426c4a065c661
|
|
7
|
+
data.tar.gz: 5f1ae0a7a15d18af2c92d35ac93718c5f5d3733fd8236197277d7ba03bef3efb370560671f704f34b7b80d3ae61d80b3fc953a8b88246a4520152ab322b435b1
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.8.0]
|
|
4
|
+
|
|
5
|
+
- Change versioning policy: the gem's major and minor version now match the
|
|
6
|
+
supported secp256k1 version. Patch releases are used for gem-side changes.
|
|
7
|
+
- Support secp256k1 v0.8.0.
|
|
8
|
+
- Add `Secp256k1::SilentPayments` module (BIP-352 Silent Payments):
|
|
9
|
+
`silentpayments_sender_create_outputs`, `silentpayments_create_label`,
|
|
10
|
+
`silentpayments_create_labeled_spend_pubkey`, `silentpayments_create_prevouts_summary` and
|
|
11
|
+
`silentpayments_scan_outputs`. Tested against the official BIP-352 test vectors.
|
|
12
|
+
The silentpayments bindings (and `secp256k1_context_set_sha256_compression`) are attached
|
|
13
|
+
optionally, so the gem remains loadable with older libsecp256k1 (e.g. v0.7.x) where these
|
|
14
|
+
features raise `Secp256k1::Error`. Availability can be checked with `silentpayments_available?`.
|
|
15
|
+
- Add FFI binding for `secp256k1_context_set_sha256_compression`.
|
|
16
|
+
- Remove FFI binding for `secp256k1_schnorrsig_sign` which was removed in secp256k1 v0.8.0.
|
|
17
|
+
Use `secp256k1_schnorrsig_sign32` instead.
|
|
18
|
+
- Fix `generate_pubkey` to return a wrong public key when a binary private key
|
|
19
|
+
happens to consist of hexadecimal characters (the key was converted twice).
|
|
20
|
+
|
|
3
21
|
## [0.3.0]
|
|
4
22
|
|
|
5
23
|
- Support secp256k1 v0.7.1.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -46,9 +46,20 @@ In addition, the following modules are also included, so you can use them as the
|
|
|
46
46
|
* [SchnorrSig](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/SchnorrSig)
|
|
47
47
|
* [MuSig](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/MuSig)
|
|
48
48
|
* [EllSwift](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/EllSwift)
|
|
49
|
+
* [SilentPayments](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/SilentPayments)
|
|
49
50
|
|
|
50
51
|
### Compatibility
|
|
51
52
|
|
|
53
|
+
Since v0.8.0, the major and minor version of this gem matches the supported
|
|
54
|
+
secp256k1 version (e.g. secp256k1rb v0.8.x supports secp256k1 v0.8.x).
|
|
55
|
+
Patch releases are used for gem-side changes.
|
|
56
|
+
|
|
57
|
+
The gem also loads with older libsecp256k1 versions; features that require a newer
|
|
58
|
+
library (e.g. the silentpayments module) raise `Secp256k1::Error` when used.
|
|
59
|
+
Use `silentpayments_available?` to check availability at runtime.
|
|
60
|
+
|
|
61
|
+
Older versions:
|
|
62
|
+
|
|
52
63
|
secp256k1 version | secp256k1rb version
|
|
53
64
|
:---:|:---:
|
|
54
65
|
v0.4.0 | v0.1.x
|
data/lib/secp256k1/c.rb
CHANGED
|
@@ -45,7 +45,6 @@ module Secp256k1
|
|
|
45
45
|
attach_function(:secp256k1_ecdsa_signature_normalize, [:pointer, :pointer, :pointer], :int)
|
|
46
46
|
attach_function(:secp256k1_ecdsa_verify, [:pointer, :pointer, :pointer, :pointer], :int)
|
|
47
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
48
|
attach_function(:secp256k1_schnorrsig_sign_custom, [:pointer, :pointer, :pointer, :size_t, :pointer, :pointer], :int)
|
|
50
49
|
attach_function(:secp256k1_schnorrsig_verify, [:pointer, :pointer, :pointer, :size_t, :pointer], :int)
|
|
51
50
|
attach_function(:secp256k1_keypair_create, [:pointer, :pointer, :pointer], :int)
|
|
@@ -94,6 +93,42 @@ module Secp256k1
|
|
|
94
93
|
attach_function(:secp256k1_musig_partial_sig_verify, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
|
|
95
94
|
attach_function(:secp256k1_musig_partial_sig_agg, [:pointer, :pointer, :pointer, :pointer, :size_t], :int)
|
|
96
95
|
|
|
96
|
+
# The following functions only exist in libsecp256k1 v0.8.0 or later, so they are
|
|
97
|
+
# attached optionally to keep this gem loadable with older libraries.
|
|
98
|
+
|
|
99
|
+
# for SilentPayments module
|
|
100
|
+
SILENTPAYMENTS_AVAILABLE = begin
|
|
101
|
+
attach_function(:secp256k1_silentpayments_sender_create_outputs, [:pointer, :pointer, :pointer, :size_t, :pointer, :pointer, :size_t, :pointer, :size_t], :int)
|
|
102
|
+
attach_function(:secp256k1_silentpayments_recipient_label_parse, [:pointer, :pointer, :pointer], :int)
|
|
103
|
+
attach_function(:secp256k1_silentpayments_recipient_label_serialize, [:pointer, :pointer, :pointer], :int)
|
|
104
|
+
attach_function(:secp256k1_silentpayments_recipient_label_create, [:pointer, :pointer, :pointer, :pointer, :uint32], :int)
|
|
105
|
+
attach_function(:secp256k1_silentpayments_recipient_create_labeled_spend_pubkey, [:pointer, :pointer, :pointer, :pointer], :int)
|
|
106
|
+
attach_function(:secp256k1_silentpayments_recipient_prevouts_summary_create, [:pointer, :pointer, :pointer, :pointer, :size_t, :pointer, :size_t], :int)
|
|
107
|
+
attach_function(:secp256k1_silentpayments_recipient_scan_outputs, [:pointer, :pointer, :pointer, :pointer, :size_t, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
|
|
108
|
+
true
|
|
109
|
+
rescue FFI::NotFoundError
|
|
110
|
+
false
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
SHA256_COMPRESSION_AVAILABLE = begin
|
|
114
|
+
attach_function(:secp256k1_context_set_sha256_compression, [:pointer, :pointer], :void)
|
|
115
|
+
true
|
|
116
|
+
rescue FFI::NotFoundError
|
|
117
|
+
false
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Whether the loaded libsecp256k1 supports the silentpayments module.
|
|
121
|
+
# @return [Boolean]
|
|
122
|
+
def self.silentpayments_available?
|
|
123
|
+
SILENTPAYMENTS_AVAILABLE
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Whether the loaded libsecp256k1 supports secp256k1_context_set_sha256_compression.
|
|
127
|
+
# @return [Boolean]
|
|
128
|
+
def self.sha256_compression_available?
|
|
129
|
+
SHA256_COMPRESSION_AVAILABLE
|
|
130
|
+
end
|
|
131
|
+
|
|
97
132
|
# Pointer to secp256k1_ellswift_xdh_hash_function_bip324 constant.
|
|
98
133
|
# @return [FFI::Pointer]
|
|
99
134
|
def self.ellswift_xdh_hash_function_bip324
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
module Secp256k1
|
|
2
|
+
|
|
3
|
+
# Silent Payments module (BIP-352).
|
|
4
|
+
# This module provides elliptic-curve operations required for sending and
|
|
5
|
+
# receiving Silent Payments. Note that this is not a full implementation of
|
|
6
|
+
# BIP-352; higher-level concepts like addresses, output script types or
|
|
7
|
+
# transactions must be handled by the caller.
|
|
8
|
+
# @example
|
|
9
|
+
# include Secp256k1
|
|
10
|
+
#
|
|
11
|
+
# # Sender side:
|
|
12
|
+
# outputs = silentpayments_sender_create_outputs(
|
|
13
|
+
# [[scan_pubkey, spend_pubkey]], outpoint_smallest, plain_seckeys: [seckey])
|
|
14
|
+
#
|
|
15
|
+
# # Recipient side:
|
|
16
|
+
# prevouts_summary = silentpayments_create_prevouts_summary(
|
|
17
|
+
# outpoint_smallest, plain_pubkeys: [pubkey])
|
|
18
|
+
# found = silentpayments_scan_outputs(
|
|
19
|
+
# outputs, scan_key, prevouts_summary, spend_pubkey)
|
|
20
|
+
module SilentPayments
|
|
21
|
+
|
|
22
|
+
# Size of a serialized Silent Payments label.
|
|
23
|
+
SP_LABEL_SIZE = 33
|
|
24
|
+
# Size of an internal Silent Payments label object.
|
|
25
|
+
SP_LABEL_INTERNAL_SIZE = 68
|
|
26
|
+
# Size of a Silent Payments prevouts summary object.
|
|
27
|
+
SP_PREVOUTS_SUMMARY_SIZE = 101
|
|
28
|
+
# Maximum number of recipients sharing the same scan public key (BIP-352).
|
|
29
|
+
SP_RECIPIENT_GROUP_LIMIT = 2323
|
|
30
|
+
|
|
31
|
+
# Struct mirroring secp256k1_silentpayments_recipient.
|
|
32
|
+
class Recipient < FFI::Struct
|
|
33
|
+
layout :scan_pubkey, [:uchar, 64],
|
|
34
|
+
:spend_pubkey, [:uchar, 64],
|
|
35
|
+
:index, :size_t
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Struct mirroring secp256k1_silentpayments_found_output.
|
|
39
|
+
class FoundOutput < FFI::Struct
|
|
40
|
+
layout :output, [:uchar, 64],
|
|
41
|
+
:tweak, [:uchar, 32],
|
|
42
|
+
:found_with_label, :int,
|
|
43
|
+
:label, [:uchar, SP_LABEL_INTERNAL_SIZE]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Whether the loaded libsecp256k1 supports the silentpayments module.
|
|
47
|
+
# The module is available in libsecp256k1 v0.8.0 or later.
|
|
48
|
+
# @return [Boolean]
|
|
49
|
+
def silentpayments_available?
|
|
50
|
+
C.silentpayments_available?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Create Silent Payments outputs for recipient(s) (sender side).
|
|
54
|
+
# @param [Array] recipients An array of recipient's key pair. Each element is an array of
|
|
55
|
+
# scan public key and (labeled) spend public key with hex format(33 bytes each).
|
|
56
|
+
# @param [String] outpoint_smallest The smallest outpoint lexicographically from the transaction inputs(36 bytes).
|
|
57
|
+
# @param [Array] plain_seckeys (Optional) An array of private keys of non-taproot inputs with hex format(32 bytes each).
|
|
58
|
+
# @param [Array] taproot_seckeys (Optional) An array of private keys of taproot inputs with hex format(32 bytes each).
|
|
59
|
+
# @return [Array] An array of x-only public keys(hex format) for the taproot outputs, ordered same as +recipients+.
|
|
60
|
+
# @raise [Secp256k1::Error] If creation failed.
|
|
61
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
62
|
+
def silentpayments_sender_create_outputs(recipients, outpoint_smallest, plain_seckeys: [], taproot_seckeys: [])
|
|
63
|
+
check_silentpayments_support!
|
|
64
|
+
raise ArgumentError, "recipients must be an Array." unless recipients.is_a?(Array)
|
|
65
|
+
raise ArgumentError, "recipients must not be empty." if recipients.empty?
|
|
66
|
+
plain_seckeys ||= []
|
|
67
|
+
taproot_seckeys ||= []
|
|
68
|
+
raise ArgumentError, "plain_seckeys must be an Array." unless plain_seckeys.is_a?(Array)
|
|
69
|
+
raise ArgumentError, "taproot_seckeys must be an Array." unless taproot_seckeys.is_a?(Array)
|
|
70
|
+
if plain_seckeys.empty? && taproot_seckeys.empty?
|
|
71
|
+
raise ArgumentError, "At least one private key(plain_seckeys or taproot_seckeys) is required."
|
|
72
|
+
end
|
|
73
|
+
validate_string!("outpoint_smallest", outpoint_smallest, 36)
|
|
74
|
+
|
|
75
|
+
with_context do |context|
|
|
76
|
+
recipient_structs = recipients.each_with_index.map do |recipient, i|
|
|
77
|
+
scan_pubkey, spend_pubkey = recipient
|
|
78
|
+
validate_string!("scan_pubkey", scan_pubkey, 33)
|
|
79
|
+
validate_string!("spend_pubkey", spend_pubkey, 33)
|
|
80
|
+
st = Recipient.new
|
|
81
|
+
st.pointer.put_bytes(0, parse_pubkey_internal(context, hex2bin(scan_pubkey)).read_string(64))
|
|
82
|
+
st.pointer.put_bytes(64, parse_pubkey_internal(context, hex2bin(spend_pubkey)).read_string(64))
|
|
83
|
+
st[:index] = i
|
|
84
|
+
st
|
|
85
|
+
end
|
|
86
|
+
recipients_ptr = FFI::MemoryPointer.new(:pointer, recipient_structs.length)
|
|
87
|
+
recipients_ptr.write_array_of_pointer(recipient_structs.map(&:pointer))
|
|
88
|
+
|
|
89
|
+
generated_outputs = Array.new(recipients.length) { FFI::MemoryPointer.new(:uchar, 64) }
|
|
90
|
+
generated_outputs_ptr = FFI::MemoryPointer.new(:pointer, generated_outputs.length)
|
|
91
|
+
generated_outputs_ptr.write_array_of_pointer(generated_outputs)
|
|
92
|
+
|
|
93
|
+
outpoint_ptr = FFI::MemoryPointer.new(:uchar, 36).put_bytes(0, hex2bin(outpoint_smallest))
|
|
94
|
+
|
|
95
|
+
keypairs = taproot_seckeys.map do |sk|
|
|
96
|
+
validate_string!("taproot_seckey", sk, 32)
|
|
97
|
+
seckey = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(sk))
|
|
98
|
+
keypair = FFI::MemoryPointer.new(:uchar, 96)
|
|
99
|
+
raise Error, "taproot_seckey is invalid." unless secp256k1_keypair_create(context, keypair, seckey) == 1
|
|
100
|
+
keypair
|
|
101
|
+
end
|
|
102
|
+
keypairs_ptr = if keypairs.empty?
|
|
103
|
+
nil
|
|
104
|
+
else
|
|
105
|
+
FFI::MemoryPointer.new(:pointer, keypairs.length).write_array_of_pointer(keypairs)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
seckeys = plain_seckeys.map do |sk|
|
|
109
|
+
validate_string!("plain_seckey", sk, 32)
|
|
110
|
+
FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(sk))
|
|
111
|
+
end
|
|
112
|
+
seckeys_ptr = if seckeys.empty?
|
|
113
|
+
nil
|
|
114
|
+
else
|
|
115
|
+
FFI::MemoryPointer.new(:pointer, seckeys.length).write_array_of_pointer(seckeys)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
result = secp256k1_silentpayments_sender_create_outputs(
|
|
119
|
+
context, generated_outputs_ptr, recipients_ptr, recipients.length, outpoint_ptr,
|
|
120
|
+
keypairs_ptr, keypairs.length, seckeys_ptr, seckeys.length)
|
|
121
|
+
raise Error, "secp256k1_silentpayments_sender_create_outputs failed." unless result == 1
|
|
122
|
+
|
|
123
|
+
generated_outputs.map { |output| serialize_xonly_pubkey_internal(context, output) }
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Create Silent Payments label tweak and label(recipient side).
|
|
128
|
+
# @param [String] scan_key The recipient's scan key with hex format(32 bytes).
|
|
129
|
+
# @param [Integer] m The integer for the m-th label (0 is used for change outputs).
|
|
130
|
+
# @return [Array(String)] An array of serialized label(33 bytes) and label tweak(32 bytes) with hex format.
|
|
131
|
+
# @raise [Secp256k1::Error] If creation failed.
|
|
132
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
133
|
+
def silentpayments_create_label(scan_key, m)
|
|
134
|
+
check_silentpayments_support!
|
|
135
|
+
validate_string!("scan_key", scan_key, 32)
|
|
136
|
+
raise ArgumentError, "m must be Integer." unless m.is_a?(Integer)
|
|
137
|
+
raise ArgumentError, "m must be between 0 and 2**32-1." if m < 0 || m > 0xffffffff
|
|
138
|
+
|
|
139
|
+
with_context do |context|
|
|
140
|
+
label = FFI::MemoryPointer.new(:uchar, SP_LABEL_INTERNAL_SIZE)
|
|
141
|
+
label_tweak = FFI::MemoryPointer.new(:uchar, 32)
|
|
142
|
+
scan_key_ptr = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(scan_key))
|
|
143
|
+
result = secp256k1_silentpayments_recipient_label_create(context, label, label_tweak, scan_key_ptr, m)
|
|
144
|
+
raise Error, "secp256k1_silentpayments_recipient_label_create failed." unless result == 1
|
|
145
|
+
out33 = FFI::MemoryPointer.new(:uchar, SP_LABEL_SIZE)
|
|
146
|
+
secp256k1_silentpayments_recipient_label_serialize(context, out33, label)
|
|
147
|
+
[out33.read_string(SP_LABEL_SIZE).unpack1('H*'), label_tweak.read_string(32).unpack1('H*')]
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Create Silent Payments labeled spend public key(recipient side).
|
|
152
|
+
# @param [String] spend_pubkey The recipient's unlabeled spend public key with hex format(33 bytes).
|
|
153
|
+
# @param [String] label The serialized label with hex format(33 bytes).
|
|
154
|
+
# @return [String] The labeled spend public key with hex format(33 bytes).
|
|
155
|
+
# @raise [Secp256k1::Error] If creation failed.
|
|
156
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
157
|
+
def silentpayments_create_labeled_spend_pubkey(spend_pubkey, label)
|
|
158
|
+
check_silentpayments_support!
|
|
159
|
+
validate_string!("spend_pubkey", spend_pubkey, 33)
|
|
160
|
+
validate_string!("label", label, SP_LABEL_SIZE)
|
|
161
|
+
|
|
162
|
+
with_context do |context|
|
|
163
|
+
spend_pubkey_ptr = parse_pubkey_internal(context, hex2bin(spend_pubkey))
|
|
164
|
+
label_ptr = parse_label_internal(context, hex2bin(label))
|
|
165
|
+
labeled = FFI::MemoryPointer.new(:uchar, 64)
|
|
166
|
+
result = secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(context, labeled, spend_pubkey_ptr, label_ptr)
|
|
167
|
+
raise Error, "secp256k1_silentpayments_recipient_create_labeled_spend_pubkey failed." unless result == 1
|
|
168
|
+
serialize_pubkey_internal(context, labeled, true)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Compute Silent Payments prevouts summary from prevout public keys and transaction inputs(recipient side).
|
|
173
|
+
# @param [String] outpoint_smallest The smallest outpoint lexicographically from the transaction inputs(36 bytes).
|
|
174
|
+
# @param [Array] plain_pubkeys (Optional) An array of public keys of non-taproot inputs with hex format(33 bytes each).
|
|
175
|
+
# @param [Array] xonly_pubkeys (Optional) An array of x-only public keys of taproot inputs with hex format(32 bytes each).
|
|
176
|
+
# @return [String] The prevouts summary data with hex format(101 bytes).
|
|
177
|
+
# Note that this is an opaque representation which is not guaranteed to be
|
|
178
|
+
# portable between different platforms or library versions.
|
|
179
|
+
# @raise [Secp256k1::Error] If the transaction is not a Silent Payments transaction.
|
|
180
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
181
|
+
def silentpayments_create_prevouts_summary(outpoint_smallest, plain_pubkeys: [], xonly_pubkeys: [])
|
|
182
|
+
check_silentpayments_support!
|
|
183
|
+
plain_pubkeys ||= []
|
|
184
|
+
xonly_pubkeys ||= []
|
|
185
|
+
raise ArgumentError, "plain_pubkeys must be an Array." unless plain_pubkeys.is_a?(Array)
|
|
186
|
+
raise ArgumentError, "xonly_pubkeys must be an Array." unless xonly_pubkeys.is_a?(Array)
|
|
187
|
+
if plain_pubkeys.empty? && xonly_pubkeys.empty?
|
|
188
|
+
raise ArgumentError, "At least one public key(plain_pubkeys or xonly_pubkeys) is required."
|
|
189
|
+
end
|
|
190
|
+
validate_string!("outpoint_smallest", outpoint_smallest, 36)
|
|
191
|
+
|
|
192
|
+
with_context do |context|
|
|
193
|
+
xonly_ptrs = xonly_pubkeys.map do |pubkey|
|
|
194
|
+
validate_string!("xonly_pubkey", pubkey, 32)
|
|
195
|
+
parse_xonly_pubkey_internal(context, hex2bin(pubkey))
|
|
196
|
+
end
|
|
197
|
+
plain_ptrs = plain_pubkeys.map do |pubkey|
|
|
198
|
+
validate_string!("plain_pubkey", pubkey, 33)
|
|
199
|
+
parse_pubkey_internal(context, hex2bin(pubkey))
|
|
200
|
+
end
|
|
201
|
+
xonly_arr = if xonly_ptrs.empty?
|
|
202
|
+
nil
|
|
203
|
+
else
|
|
204
|
+
FFI::MemoryPointer.new(:pointer, xonly_ptrs.length).write_array_of_pointer(xonly_ptrs)
|
|
205
|
+
end
|
|
206
|
+
plain_arr = if plain_ptrs.empty?
|
|
207
|
+
nil
|
|
208
|
+
else
|
|
209
|
+
FFI::MemoryPointer.new(:pointer, plain_ptrs.length).write_array_of_pointer(plain_ptrs)
|
|
210
|
+
end
|
|
211
|
+
outpoint_ptr = FFI::MemoryPointer.new(:uchar, 36).put_bytes(0, hex2bin(outpoint_smallest))
|
|
212
|
+
prevouts_summary = FFI::MemoryPointer.new(:uchar, SP_PREVOUTS_SUMMARY_SIZE)
|
|
213
|
+
result = secp256k1_silentpayments_recipient_prevouts_summary_create(
|
|
214
|
+
context, prevouts_summary, outpoint_ptr, xonly_arr, xonly_ptrs.length, plain_arr, plain_ptrs.length)
|
|
215
|
+
raise Error, "secp256k1_silentpayments_recipient_prevouts_summary_create failed." unless result == 1
|
|
216
|
+
prevouts_summary.read_string(SP_PREVOUTS_SUMMARY_SIZE).unpack1('H*')
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Scan for Silent Payments transaction outputs(recipient side).
|
|
221
|
+
# @param [Array] tx_outputs An array of the transaction's x-only public key outputs with hex format(32 bytes each),
|
|
222
|
+
# in their original transaction (vout) order.
|
|
223
|
+
# @param [String] scan_key The recipient's scan key with hex format(32 bytes).
|
|
224
|
+
# @param [String] prevouts_summary The prevouts summary data created by #silentpayments_create_prevouts_summary
|
|
225
|
+
# with hex format(101 bytes).
|
|
226
|
+
# @param [String] spend_pubkey The recipient's unlabeled spend public key with hex format(33 bytes).
|
|
227
|
+
# @param [Hash] labels (Optional) The recipient's label cache. A hash whose key is a serialized label(33 bytes)
|
|
228
|
+
# and whose value is the corresponding label tweak(32 bytes), both with hex format.
|
|
229
|
+
# Can be created with #silentpayments_create_label.
|
|
230
|
+
# @return [Array] An array of found outputs. Each element is a hash with the following keys:
|
|
231
|
+
# - :output x-only public key of the found output with hex format(32 bytes).
|
|
232
|
+
# - :tweak The 32-byte tweak needed to spend the output with hex format.
|
|
233
|
+
# - :label The serialized label(33 bytes with hex format) if the output was sent to a labeled address, otherwise nil.
|
|
234
|
+
# @raise [Secp256k1::Error] If the transaction is not a Silent Payments transaction or arguments are invalid.
|
|
235
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
|
236
|
+
def silentpayments_scan_outputs(tx_outputs, scan_key, prevouts_summary, spend_pubkey, labels: nil)
|
|
237
|
+
check_silentpayments_support!
|
|
238
|
+
raise ArgumentError, "tx_outputs must be an Array." unless tx_outputs.is_a?(Array)
|
|
239
|
+
raise ArgumentError, "tx_outputs must not be empty." if tx_outputs.empty?
|
|
240
|
+
validate_string!("scan_key", scan_key, 32)
|
|
241
|
+
validate_string!("prevouts_summary", prevouts_summary, SP_PREVOUTS_SUMMARY_SIZE)
|
|
242
|
+
validate_string!("spend_pubkey", spend_pubkey, 33)
|
|
243
|
+
raise ArgumentError, "labels must be a Hash." if labels && !labels.is_a?(Hash)
|
|
244
|
+
|
|
245
|
+
with_context do |context|
|
|
246
|
+
output_ptrs = tx_outputs.map do |output|
|
|
247
|
+
validate_string!("tx_output", output, 32)
|
|
248
|
+
parse_xonly_pubkey_internal(context, hex2bin(output))
|
|
249
|
+
end
|
|
250
|
+
tx_outputs_ptr = FFI::MemoryPointer.new(:pointer, output_ptrs.length).write_array_of_pointer(output_ptrs)
|
|
251
|
+
|
|
252
|
+
found_outputs = Array.new(tx_outputs.length) { FoundOutput.new }
|
|
253
|
+
found_outputs_ptr = FFI::MemoryPointer.new(:pointer, found_outputs.length)
|
|
254
|
+
found_outputs_ptr.write_array_of_pointer(found_outputs.map(&:pointer))
|
|
255
|
+
n_found = FFI::MemoryPointer.new(:uint32)
|
|
256
|
+
|
|
257
|
+
scan_key_ptr = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(scan_key))
|
|
258
|
+
prevouts_summary_ptr = FFI::MemoryPointer.new(:uchar, SP_PREVOUTS_SUMMARY_SIZE).
|
|
259
|
+
put_bytes(0, hex2bin(prevouts_summary))
|
|
260
|
+
spend_pubkey_ptr = parse_pubkey_internal(context, hex2bin(spend_pubkey))
|
|
261
|
+
|
|
262
|
+
label_lookup = nil
|
|
263
|
+
if labels
|
|
264
|
+
# Keep tweak pointers referenced in this hash so they remain valid while scanning.
|
|
265
|
+
tweak_cache = {}
|
|
266
|
+
labels.each do |label, tweak|
|
|
267
|
+
validate_string!("label", label, SP_LABEL_SIZE)
|
|
268
|
+
validate_string!("label_tweak", tweak, 32)
|
|
269
|
+
tweak_cache[hex2bin(label)] = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(tweak))
|
|
270
|
+
end
|
|
271
|
+
label_lookup = FFI::Function.new(:pointer, [:pointer, :pointer]) do |label33, _|
|
|
272
|
+
tweak_cache[label33.read_string(SP_LABEL_SIZE)] || FFI::Pointer::NULL
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
result = secp256k1_silentpayments_recipient_scan_outputs(
|
|
277
|
+
context, found_outputs_ptr, n_found, tx_outputs_ptr, tx_outputs.length,
|
|
278
|
+
scan_key_ptr, prevouts_summary_ptr, spend_pubkey_ptr, label_lookup, nil)
|
|
279
|
+
raise Error, "secp256k1_silentpayments_recipient_scan_outputs failed." unless result == 1
|
|
280
|
+
|
|
281
|
+
Array.new(n_found.read_uint32) do |i|
|
|
282
|
+
found = found_outputs[i]
|
|
283
|
+
label = if found[:found_with_label] != 0
|
|
284
|
+
out33 = FFI::MemoryPointer.new(:uchar, SP_LABEL_SIZE)
|
|
285
|
+
secp256k1_silentpayments_recipient_label_serialize(
|
|
286
|
+
context, out33, found.pointer + FoundOutput.offset_of(:label))
|
|
287
|
+
out33.read_string(SP_LABEL_SIZE).unpack1('H*')
|
|
288
|
+
end
|
|
289
|
+
{
|
|
290
|
+
output: serialize_xonly_pubkey_internal(context, found.pointer),
|
|
291
|
+
tweak: found.pointer.get_bytes(FoundOutput.offset_of(:tweak), 32).unpack1('H*'),
|
|
292
|
+
label: label
|
|
293
|
+
}
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
private
|
|
299
|
+
|
|
300
|
+
# @raise [Secp256k1::Error] If the loaded libsecp256k1 does not support the silentpayments module.
|
|
301
|
+
def check_silentpayments_support!
|
|
302
|
+
unless C.silentpayments_available?
|
|
303
|
+
raise Error, "The loaded libsecp256k1 does not support the silentpayments module. libsecp256k1 v0.8.0 or later is required."
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Parse a serialized label into an internal label pointer.
|
|
308
|
+
# @param [FFI::Pointer] context secp256k1 context.
|
|
309
|
+
# @param [String] label serialized label with binary format(33 bytes).
|
|
310
|
+
# @return [FFI::Pointer] internal label pointer(68 bytes).
|
|
311
|
+
# @raise [Secp256k1::Error] If the label is invalid.
|
|
312
|
+
def parse_label_internal(context, label)
|
|
313
|
+
label_ptr = FFI::MemoryPointer.new(:uchar, SP_LABEL_SIZE).put_bytes(0, label)
|
|
314
|
+
internal = FFI::MemoryPointer.new(:uchar, SP_LABEL_INTERNAL_SIZE)
|
|
315
|
+
raise Error, 'An invalid label was specified.' unless secp256k1_silentpayments_recipient_label_parse(context, internal, label_ptr) == 1
|
|
316
|
+
internal
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
end
|
data/lib/secp256k1/version.rb
CHANGED
data/lib/secp256k1.rb
CHANGED
|
@@ -9,6 +9,7 @@ require_relative 'secp256k1/recovery'
|
|
|
9
9
|
require_relative 'secp256k1/ellswift'
|
|
10
10
|
require_relative 'secp256k1/schnorrsig'
|
|
11
11
|
require_relative 'secp256k1/musig'
|
|
12
|
+
require_relative 'secp256k1/silentpayments'
|
|
12
13
|
|
|
13
14
|
# Binding for secp256k1 (https://github.com/bitcoin-core/secp256k1/)
|
|
14
15
|
# @example
|
|
@@ -36,6 +37,7 @@ module Secp256k1
|
|
|
36
37
|
include SchnorrSig
|
|
37
38
|
include EllSwift
|
|
38
39
|
include MuSig
|
|
40
|
+
include SilentPayments
|
|
39
41
|
|
|
40
42
|
FLAGS_TYPE_MASK = ((1 << 8) - 1)
|
|
41
43
|
FLAGS_TYPE_CONTEXT = (1 << 0)
|
|
@@ -88,8 +90,8 @@ module Secp256k1
|
|
|
88
90
|
private_key = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, SecureRandom.random_bytes(32))
|
|
89
91
|
ret = secp256k1_ec_seckey_verify(context, private_key)
|
|
90
92
|
end
|
|
91
|
-
private_key =
|
|
92
|
-
[private_key
|
|
93
|
+
private_key = private_key.read_string(32)
|
|
94
|
+
[private_key.unpack1('H*'), generate_pubkey_in_context(context, private_key, compressed: compressed)]
|
|
93
95
|
end
|
|
94
96
|
end
|
|
95
97
|
|
|
@@ -323,9 +325,12 @@ module Secp256k1
|
|
|
323
325
|
output.read_string(X_ONLY_PUBKEY_SIZE).unpack1('H*')
|
|
324
326
|
end
|
|
325
327
|
|
|
328
|
+
# Generate public key from +private_key+ using given +context+.
|
|
329
|
+
# +private_key+ must be a 32-byte binary string, not hex, to avoid a second
|
|
330
|
+
# hex conversion corrupting binary keys that happen to look like hex strings.
|
|
326
331
|
def generate_pubkey_in_context(context, private_key, compressed: true)
|
|
327
332
|
internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
|
|
328
|
-
priv_ptr = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0,
|
|
333
|
+
priv_ptr = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, private_key)
|
|
329
334
|
result = secp256k1_ec_pubkey_create(context, internal_pubkey, priv_ptr)
|
|
330
335
|
raise 'error creating pubkey' unless result
|
|
331
336
|
serialize_pubkey_internal(context, internal_pubkey, compressed)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: secp256k1rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- azuchi
|
|
@@ -51,6 +51,7 @@ files:
|
|
|
51
51
|
- lib/secp256k1/musig/session.rb
|
|
52
52
|
- lib/secp256k1/recovery.rb
|
|
53
53
|
- lib/secp256k1/schnorrsig.rb
|
|
54
|
+
- lib/secp256k1/silentpayments.rb
|
|
54
55
|
- lib/secp256k1/version.rb
|
|
55
56
|
- secp256k1rb.gemspec
|
|
56
57
|
- sig/secp256k1.rbs
|