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

Parameters:

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.

Parameters:

  • input (String)

    Packet to be decrypt.

  • aad (String) (defaults to: '')

    AAD

  • ignore (Boolean) (defaults to: false)

    Whether contains ignore bit or not.

Returns:

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.

Parameters:

  • contents (String)

    Packet with binary format.

  • aad (String) (defaults to: '')

    AAD

  • ignore (Boolean) (defaults to: false)

    Whether contains ignore bit or not.

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.

Parameters:

  • their_pubkey (Bitcoin::BIP324::EllSwiftPubkey)
  • initiator (Boolean)

    Set true if we are the initiator establishing the v2 P2P connection.

  • self_decrypt (Boolean) (defaults to: false)

    only for testing, and swaps encryption/decryption keys, so that encryption



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