Class: Bitcoin::BIP324::Cipher

Inherits:
Object
  • Object
show all
Includes:
Util
Defined in:
lib/bitcoin/bip324/cipher.rb

Overview

The BIP324 packet cipher, encapsulating its key derivation, stream cipher, and AEAD.

Constant Summary collapse

HEADER =
[1 << 7].pack('C')
HEADER_LEN =
1
LENGTH_LEN =
3
EXPANSION =
LENGTH_LEN + HEADER_LEN + 16

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Util

#byte_to_bit, #calc_checksum, #decode_base58_address, #double_sha256, #encode_base58_address, #hash160, #hkdf_sha256, #hmac_sha256, #pack_boolean, #pack_var_int, #pack_var_string, #padding_zero, #sha256, #tagged_hash, #unpack_boolean, #unpack_var_int, #unpack_var_int_from_io, #unpack_var_string, #valid_address?

Constructor Details

#initialize(key, our_pubkey = nil) ⇒ Cipher

Constructor

Raises:

  • ArgumentError



27
28
29
30
31
32
# File 'lib/bitcoin/bip324/cipher.rb', line 27

def initialize(key, our_pubkey = nil)
  raise ArgumentError, "key must be Bitcoin::Key" unless key.is_a?(Bitcoin::Key)
  raise ArgumentError, "our_pubkey must be Bitcoin::BIP324::EllSwiftPubkey" if our_pubkey && !our_pubkey.is_a?(Bitcoin::BIP324::EllSwiftPubkey)
  @our_pubkey = our_pubkey ? our_pubkey : key.create_ell_pubkey
  @key = key
end

Instance Attribute Details

#keyObject (readonly)

Returns the value of attribute key.



12
13
14
# File 'lib/bitcoin/bip324/cipher.rb', line 12

def key
  @key
end

#our_pubkeyObject (readonly)

Returns the value of attribute our_pubkey.



13
14
15
# File 'lib/bitcoin/bip324/cipher.rb', line 13

def our_pubkey
  @our_pubkey
end

#recv_garbage_terminatorObject

Returns the value of attribute recv_garbage_terminator.



17
18
19
# File 'lib/bitcoin/bip324/cipher.rb', line 17

def recv_garbage_terminator
  @recv_garbage_terminator
end

#recv_l_cipherObject

Returns the value of attribute recv_l_cipher.



20
21
22
# File 'lib/bitcoin/bip324/cipher.rb', line 20

def recv_l_cipher
  @recv_l_cipher
end

#recv_p_cipherObject

Returns the value of attribute recv_p_cipher.



21
22
23
# File 'lib/bitcoin/bip324/cipher.rb', line 21

def recv_p_cipher
  @recv_p_cipher
end

#send_garbage_terminatorObject

Returns the value of attribute send_garbage_terminator.



16
17
18
# File 'lib/bitcoin/bip324/cipher.rb', line 16

def send_garbage_terminator
  @send_garbage_terminator
end

#send_l_cipherObject

Returns the value of attribute send_l_cipher.



18
19
20
# File 'lib/bitcoin/bip324/cipher.rb', line 18

def send_l_cipher
  @send_l_cipher
end

#send_p_cipherObject

Returns the value of attribute send_p_cipher.



19
20
21
# File 'lib/bitcoin/bip324/cipher.rb', line 19

def send_p_cipher
  @send_p_cipher
end

#session_idObject

Returns the value of attribute session_id.



15
16
17
# File 'lib/bitcoin/bip324/cipher.rb', line 15

def session_id
  @session_id
end

Instance Method Details

#decrypt(input, aad: '', ignore: false) ⇒ String

Decrypt a packet. Only after setup.

Raises:

  • Bitcoin::BIP324::InvalidPaketLength



93
94
95
96
97
# File 'lib/bitcoin/bip324/cipher.rb', line 93

def decrypt(input, aad: '', ignore: false)
  len = decrypt_length(input[0...Bitcoin::BIP324::Cipher::LENGTH_LEN])
  raise Bitcoin::BIP324::InvalidPaketLength unless input.bytesize == len + EXPANSION
  recv_p_cipher.decrypt(aad, input[Bitcoin::BIP324::Cipher::LENGTH_LEN..-1])
end

#encrypt(contents, aad: '', ignore: false) ⇒ Object

Encrypt a packet. Only after setup.

Raises:

  • Bitcoin::BIP324::TooLargeContent



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/bitcoin/bip324/cipher.rb', line 70

def encrypt(contents, aad: '', ignore: false)
  raise Bitcoin::BIP324::TooLargeContent unless contents.bytesize <= (2**24 - 1)

  # encrypt length
  len = Array.new(3)
  len[0] = contents.bytesize & 0xff
  len[1] = (contents.bytesize >> 8) & 0xff
  len[2] = (contents.bytesize >> 16) & 0xff
  enc_plaintext_len = send_l_cipher.encrypt(len.pack('C*'))

  # encrypt contents
  header = ignore ? HEADER : "00".htb
  plaintext = header + contents
  aead_ciphertext = send_p_cipher.encrypt(aad, plaintext)
  enc_plaintext_len + aead_ciphertext
end

#setup(their_pubkey, initiator, self_decrypt = false) ⇒ Object

Setup when the other side’s public key is received. and decryption can be tested without knowing the other side’s private key.



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/bitcoin/bip324/cipher.rb', line 39

def setup(their_pubkey, initiator, self_decrypt = false)
  salt = 'bitcoin_v2_shared_secret' + Bitcoin.chain_params.magic_head.htb
  ecdh_secret = BIP324.v2_ecdh(key.priv_key, their_pubkey, our_pubkey, initiator).htb
  terminator = hkdf_sha256(ecdh_secret, salt, 'garbage_terminators')
  side = initiator != self_decrypt
  if side
    self.send_l_cipher = FSChaCha20.new(hkdf_sha256(ecdh_secret, salt, 'initiator_L'))
    self.send_p_cipher = FSChaCha20Poly1305.new(hkdf_sha256(ecdh_secret, salt, 'initiator_P'))
    self.recv_l_cipher = FSChaCha20.new(hkdf_sha256(ecdh_secret, salt, 'responder_L'))
    self.recv_p_cipher = FSChaCha20Poly1305.new(hkdf_sha256(ecdh_secret, salt, 'responder_P'))
  else
    self.recv_l_cipher = FSChaCha20.new(hkdf_sha256(ecdh_secret, salt, 'initiator_L'))
    self.recv_p_cipher = FSChaCha20Poly1305.new(hkdf_sha256(ecdh_secret, salt, 'initiator_P'))
    self.send_l_cipher = FSChaCha20.new(hkdf_sha256(ecdh_secret, salt, 'responder_L'))
    self.send_p_cipher = FSChaCha20Poly1305.new(hkdf_sha256(ecdh_secret, salt, 'responder_P'))
  end
  if initiator
    self.send_garbage_terminator = terminator[0...16].bth
    self.recv_garbage_terminator = terminator[16..-1].bth
  else
    self.recv_garbage_terminator = terminator[0...16].bth
    self.send_garbage_terminator = terminator[16..-1].bth
  end
  self.session_id = hkdf_sha256(ecdh_secret, salt, 'session_id').bth
end