Module: Secp256k1::SilentPayments
- Included in:
- Secp256k1
- Defined in:
- lib/secp256k1/silentpayments.rb
Overview
Silent Payments module (BIP-352). This module provides elliptic-curve operations required for sending and receiving Silent Payments. Note that this is not a full implementation of BIP-352; higher-level concepts like addresses, output script types or transactions must be handled by the caller.
Defined Under Namespace
Classes: FoundOutput, Recipient
Constant Summary collapse
- SP_LABEL_SIZE =
Size of a serialized Silent Payments label.
33- SP_LABEL_INTERNAL_SIZE =
Size of an internal Silent Payments label object.
68- SP_PREVOUTS_SUMMARY_SIZE =
Size of a Silent Payments prevouts summary object.
101- SP_RECIPIENT_GROUP_LIMIT =
Maximum number of recipients sharing the same scan public key (BIP-352).
2323
Instance Method Summary collapse
-
#silentpayments_available? ⇒ Boolean
Whether the loaded libsecp256k1 supports the silentpayments module.
-
#silentpayments_create_label(scan_key, m) ⇒ Array(String)
Create Silent Payments label tweak and label(recipient side).
-
#silentpayments_create_labeled_spend_pubkey(spend_pubkey, label) ⇒ String
Create Silent Payments labeled spend public key(recipient side).
-
#silentpayments_create_prevouts_summary(outpoint_smallest, plain_pubkeys: [], xonly_pubkeys: []) ⇒ String
Compute Silent Payments prevouts summary from prevout public keys and transaction inputs(recipient side).
-
#silentpayments_scan_outputs(tx_outputs, scan_key, prevouts_summary, spend_pubkey, labels: nil) ⇒ Array
Scan for Silent Payments transaction outputs(recipient side).
-
#silentpayments_sender_create_outputs(recipients, outpoint_smallest, plain_seckeys: [], taproot_seckeys: []) ⇒ Array
Create Silent Payments outputs for recipient(s) (sender side).
Instance Method Details
#silentpayments_available? ⇒ Boolean
Whether the loaded libsecp256k1 supports the silentpayments module. The module is available in libsecp256k1 v0.8.0 or later.
49 50 51 |
# File 'lib/secp256k1/silentpayments.rb', line 49 def silentpayments_available? C.silentpayments_available? end |
#silentpayments_create_label(scan_key, m) ⇒ Array(String)
Create Silent Payments label tweak and label(recipient side).
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/secp256k1/silentpayments.rb', line 133 def silentpayments_create_label(scan_key, m) check_silentpayments_support! validate_string!("scan_key", scan_key, 32) raise ArgumentError, "m must be Integer." unless m.is_a?(Integer) raise ArgumentError, "m must be between 0 and 2**32-1." if m < 0 || m > 0xffffffff with_context do |context| label = FFI::MemoryPointer.new(:uchar, SP_LABEL_INTERNAL_SIZE) label_tweak = FFI::MemoryPointer.new(:uchar, 32) scan_key_ptr = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(scan_key)) result = secp256k1_silentpayments_recipient_label_create(context, label, label_tweak, scan_key_ptr, m) raise Error, "secp256k1_silentpayments_recipient_label_create failed." unless result == 1 out33 = FFI::MemoryPointer.new(:uchar, SP_LABEL_SIZE) secp256k1_silentpayments_recipient_label_serialize(context, out33, label) [out33.read_string(SP_LABEL_SIZE).unpack1('H*'), label_tweak.read_string(32).unpack1('H*')] end end |
#silentpayments_create_labeled_spend_pubkey(spend_pubkey, label) ⇒ String
Create Silent Payments labeled spend public key(recipient side).
157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
# File 'lib/secp256k1/silentpayments.rb', line 157 def silentpayments_create_labeled_spend_pubkey(spend_pubkey, label) check_silentpayments_support! validate_string!("spend_pubkey", spend_pubkey, 33) validate_string!("label", label, SP_LABEL_SIZE) with_context do |context| spend_pubkey_ptr = parse_pubkey_internal(context, hex2bin(spend_pubkey)) label_ptr = parse_label_internal(context, hex2bin(label)) labeled = FFI::MemoryPointer.new(:uchar, 64) result = secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(context, labeled, spend_pubkey_ptr, label_ptr) raise Error, "secp256k1_silentpayments_recipient_create_labeled_spend_pubkey failed." unless result == 1 serialize_pubkey_internal(context, labeled, true) end end |
#silentpayments_create_prevouts_summary(outpoint_smallest, plain_pubkeys: [], xonly_pubkeys: []) ⇒ String
Compute Silent Payments prevouts summary from prevout public keys and transaction inputs(recipient side).
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/secp256k1/silentpayments.rb', line 181 def silentpayments_create_prevouts_summary(outpoint_smallest, plain_pubkeys: [], xonly_pubkeys: []) check_silentpayments_support! plain_pubkeys ||= [] xonly_pubkeys ||= [] raise ArgumentError, "plain_pubkeys must be an Array." unless plain_pubkeys.is_a?(Array) raise ArgumentError, "xonly_pubkeys must be an Array." unless xonly_pubkeys.is_a?(Array) if plain_pubkeys.empty? && xonly_pubkeys.empty? raise ArgumentError, "At least one public key(plain_pubkeys or xonly_pubkeys) is required." end validate_string!("outpoint_smallest", outpoint_smallest, 36) with_context do |context| xonly_ptrs = xonly_pubkeys.map do |pubkey| validate_string!("xonly_pubkey", pubkey, 32) parse_xonly_pubkey_internal(context, hex2bin(pubkey)) end plain_ptrs = plain_pubkeys.map do |pubkey| validate_string!("plain_pubkey", pubkey, 33) parse_pubkey_internal(context, hex2bin(pubkey)) end xonly_arr = if xonly_ptrs.empty? nil else FFI::MemoryPointer.new(:pointer, xonly_ptrs.length).write_array_of_pointer(xonly_ptrs) end plain_arr = if plain_ptrs.empty? nil else FFI::MemoryPointer.new(:pointer, plain_ptrs.length).write_array_of_pointer(plain_ptrs) end outpoint_ptr = FFI::MemoryPointer.new(:uchar, 36).put_bytes(0, hex2bin(outpoint_smallest)) prevouts_summary = FFI::MemoryPointer.new(:uchar, SP_PREVOUTS_SUMMARY_SIZE) result = secp256k1_silentpayments_recipient_prevouts_summary_create( context, prevouts_summary, outpoint_ptr, xonly_arr, xonly_ptrs.length, plain_arr, plain_ptrs.length) raise Error, "secp256k1_silentpayments_recipient_prevouts_summary_create failed." unless result == 1 prevouts_summary.read_string(SP_PREVOUTS_SUMMARY_SIZE).unpack1('H*') end end |
#silentpayments_scan_outputs(tx_outputs, scan_key, prevouts_summary, spend_pubkey, labels: nil) ⇒ Array
Scan for Silent Payments transaction outputs(recipient side).
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 |
# File 'lib/secp256k1/silentpayments.rb', line 236 def silentpayments_scan_outputs(tx_outputs, scan_key, prevouts_summary, spend_pubkey, labels: nil) check_silentpayments_support! raise ArgumentError, "tx_outputs must be an Array." unless tx_outputs.is_a?(Array) raise ArgumentError, "tx_outputs must not be empty." if tx_outputs.empty? validate_string!("scan_key", scan_key, 32) validate_string!("prevouts_summary", prevouts_summary, SP_PREVOUTS_SUMMARY_SIZE) validate_string!("spend_pubkey", spend_pubkey, 33) raise ArgumentError, "labels must be a Hash." if labels && !labels.is_a?(Hash) with_context do |context| output_ptrs = tx_outputs.map do |output| validate_string!("tx_output", output, 32) parse_xonly_pubkey_internal(context, hex2bin(output)) end tx_outputs_ptr = FFI::MemoryPointer.new(:pointer, output_ptrs.length).write_array_of_pointer(output_ptrs) found_outputs = Array.new(tx_outputs.length) { FoundOutput.new } found_outputs_ptr = FFI::MemoryPointer.new(:pointer, found_outputs.length) found_outputs_ptr.write_array_of_pointer(found_outputs.map(&:pointer)) n_found = FFI::MemoryPointer.new(:uint32) scan_key_ptr = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(scan_key)) prevouts_summary_ptr = FFI::MemoryPointer.new(:uchar, SP_PREVOUTS_SUMMARY_SIZE). put_bytes(0, hex2bin(prevouts_summary)) spend_pubkey_ptr = parse_pubkey_internal(context, hex2bin(spend_pubkey)) label_lookup = nil if labels # Keep tweak pointers referenced in this hash so they remain valid while scanning. tweak_cache = {} labels.each do |label, tweak| validate_string!("label", label, SP_LABEL_SIZE) validate_string!("label_tweak", tweak, 32) tweak_cache[hex2bin(label)] = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(tweak)) end label_lookup = FFI::Function.new(:pointer, [:pointer, :pointer]) do |label33, _| tweak_cache[label33.read_string(SP_LABEL_SIZE)] || FFI::Pointer::NULL end end result = secp256k1_silentpayments_recipient_scan_outputs( context, found_outputs_ptr, n_found, tx_outputs_ptr, tx_outputs.length, scan_key_ptr, prevouts_summary_ptr, spend_pubkey_ptr, label_lookup, nil) raise Error, "secp256k1_silentpayments_recipient_scan_outputs failed." unless result == 1 Array.new(n_found.read_uint32) do |i| found = found_outputs[i] label = if found[:found_with_label] != 0 out33 = FFI::MemoryPointer.new(:uchar, SP_LABEL_SIZE) secp256k1_silentpayments_recipient_label_serialize( context, out33, found.pointer + FoundOutput.offset_of(:label)) out33.read_string(SP_LABEL_SIZE).unpack1('H*') end { output: serialize_xonly_pubkey_internal(context, found.pointer), tweak: found.pointer.get_bytes(FoundOutput.offset_of(:tweak), 32).unpack1('H*'), label: label } end end end |
#silentpayments_sender_create_outputs(recipients, outpoint_smallest, plain_seckeys: [], taproot_seckeys: []) ⇒ Array
Create Silent Payments outputs for recipient(s) (sender side).
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/secp256k1/silentpayments.rb', line 62 def silentpayments_sender_create_outputs(recipients, outpoint_smallest, plain_seckeys: [], taproot_seckeys: []) check_silentpayments_support! raise ArgumentError, "recipients must be an Array." unless recipients.is_a?(Array) raise ArgumentError, "recipients must not be empty." if recipients.empty? plain_seckeys ||= [] taproot_seckeys ||= [] raise ArgumentError, "plain_seckeys must be an Array." unless plain_seckeys.is_a?(Array) raise ArgumentError, "taproot_seckeys must be an Array." unless taproot_seckeys.is_a?(Array) if plain_seckeys.empty? && taproot_seckeys.empty? raise ArgumentError, "At least one private key(plain_seckeys or taproot_seckeys) is required." end validate_string!("outpoint_smallest", outpoint_smallest, 36) with_context do |context| recipient_structs = recipients.each_with_index.map do |recipient, i| scan_pubkey, spend_pubkey = recipient validate_string!("scan_pubkey", scan_pubkey, 33) validate_string!("spend_pubkey", spend_pubkey, 33) st = Recipient.new st.pointer.put_bytes(0, parse_pubkey_internal(context, hex2bin(scan_pubkey)).read_string(64)) st.pointer.put_bytes(64, parse_pubkey_internal(context, hex2bin(spend_pubkey)).read_string(64)) st[:index] = i st end recipients_ptr = FFI::MemoryPointer.new(:pointer, recipient_structs.length) recipients_ptr.write_array_of_pointer(recipient_structs.map(&:pointer)) generated_outputs = Array.new(recipients.length) { FFI::MemoryPointer.new(:uchar, 64) } generated_outputs_ptr = FFI::MemoryPointer.new(:pointer, generated_outputs.length) generated_outputs_ptr.write_array_of_pointer(generated_outputs) outpoint_ptr = FFI::MemoryPointer.new(:uchar, 36).put_bytes(0, hex2bin(outpoint_smallest)) keypairs = taproot_seckeys.map do |sk| validate_string!("taproot_seckey", sk, 32) seckey = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(sk)) keypair = FFI::MemoryPointer.new(:uchar, 96) raise Error, "taproot_seckey is invalid." unless secp256k1_keypair_create(context, keypair, seckey) == 1 keypair end keypairs_ptr = if keypairs.empty? nil else FFI::MemoryPointer.new(:pointer, keypairs.length).write_array_of_pointer(keypairs) end seckeys = plain_seckeys.map do |sk| validate_string!("plain_seckey", sk, 32) FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(sk)) end seckeys_ptr = if seckeys.empty? nil else FFI::MemoryPointer.new(:pointer, seckeys.length).write_array_of_pointer(seckeys) end result = secp256k1_silentpayments_sender_create_outputs( context, generated_outputs_ptr, recipients_ptr, recipients.length, outpoint_ptr, keypairs_ptr, keypairs.length, seckeys_ptr, seckeys.length) raise Error, "secp256k1_silentpayments_sender_create_outputs failed." unless result == 1 generated_outputs.map { |output| serialize_xonly_pubkey_internal(context, output) } end end |