Module: Mpp::Methods::Tempo::Attribution

Defined in:
lib/mpp/methods/tempo/attribution.rb

Defined Under Namespace

Classes: DecodedMemo

Constant Summary collapse

VERSION =
0x01
ANONYMOUS =
"\x00" * 10

Class Method Summary collapse

Class Method Details

.decode(memo) ⇒ Object

Decode an MPP attribution memo.



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/mpp/methods/tempo/attribution.rb', line 87

def decode(memo)
  return nil unless mpp_memo?(memo)

  begin
    version = memo[10, 2].to_i(16)
    server_fingerprint = "0x#{memo[12, 20]}"
    client_hex = memo[32, 20]
    nonce = "0x#{memo[52..]}"

    client_bytes = [client_hex].pack("H*")
    client_fingerprint = (client_bytes == ANONYMOUS.b) ? nil : "0x#{client_hex}"
  rescue ArgumentError
    return nil
  end

  DecodedMemo.new(
    version: version,
    server_fingerprint: server_fingerprint,
    client_fingerprint: client_fingerprint,
    nonce: nonce
  )
end

.encode(server_id:, client_id: nil, challenge_id: nil) ⇒ Object

Encode an MPP attribution memo (32 bytes).

Byte Layout:

0..3:   TAG = keccak256("mpp")[0:4]
4:      version (0x01)
5..14:  serverId fingerprint
15..24: clientId fingerprint or zeros
25..31: random nonce


44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/mpp/methods/tempo/attribution.rb', line 44

def encode(server_id:, client_id: nil, challenge_id: nil)
  buf = "\x00".b * 32
  buf[0, 4] = tag
  buf[4] = [VERSION].pack("C")
  buf[5, 10] = fingerprint(server_id)
  buf[15, 10] = client_id ? fingerprint(client_id) : ANONYMOUS.b
  buf[25, 7] = if challenge_id
    keccak256(challenge_id.encode(Encoding::UTF_8))[0, 7]
  else
    SecureRandom.random_bytes(7)
  end
  "0x#{buf.unpack1("H*")}"
end

.fingerprint(value) ⇒ Object



32
33
34
# File 'lib/mpp/methods/tempo/attribution.rb', line 32

def fingerprint(value)
  keccak256(value.encode(Encoding::UTF_8))[0, 10]
end

.keccak256(data) ⇒ Object

Compute keccak256 hash. Uses OpenSSL if available, otherwise pure Ruby.



17
18
19
20
21
22
23
24
25
# File 'lib/mpp/methods/tempo/attribution.rb', line 17

def keccak256(data)
  # Try eth gem's keccak first
  Kernel.require "eth"
  Eth::Util.keccak256(data)
rescue LoadError
  # Fallback: use OpenSSL's SHA3-256 (not exactly keccak, but close)
  # For production, the eth gem should be installed
  OpenSSL::Digest.new("SHA3-256").digest(data)
end

.mpp_memo?(memo) ⇒ Boolean

Check if a memo is an MPP attribution memo.

Returns:

  • (Boolean)


59
60
61
62
63
64
65
66
67
68
69
# File 'lib/mpp/methods/tempo/attribution.rb', line 59

def mpp_memo?(memo)
  return false unless memo.length == 66

  begin
    memo_tag = [memo[2, 8]].pack("H*")
    memo_version = memo[10, 2].to_i(16)
  rescue ArgumentError
    return false
  end
  memo_tag == tag && memo_version == VERSION
end

.tagObject

Compute TAG = keccak256(“mpp”)



28
29
30
# File 'lib/mpp/methods/tempo/attribution.rb', line 28

def tag
  @tag ||= keccak256("mpp".b)[0, 4]
end

.verify_server(memo, server_id) ⇒ Object

Verify server fingerprint in memo.



72
73
74
75
76
77
78
79
80
81
# File 'lib/mpp/methods/tempo/attribution.rb', line 72

def verify_server(memo, server_id)
  return false unless mpp_memo?(memo)

  begin
    memo_server = [memo[12, 20]].pack("H*")
  rescue ArgumentError
    return false
  end
  memo_server == fingerprint(server_id)
end