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
-
.decode(memo) ⇒ Object
Decode an MPP attribution memo.
-
.encode(server_id:, client_id: nil, challenge_id: nil) ⇒ Object
Encode an MPP attribution memo (32 bytes).
- .fingerprint(value) ⇒ Object
-
.keccak256(data) ⇒ Object
Ethereum Keccak-256.
-
.mpp_memo?(memo) ⇒ Boolean
Check if a memo is an MPP attribution memo.
-
.tag ⇒ Object
Compute TAG = keccak256("mpp")[0:4].
-
.verify_challenge_binding(memo, challenge_id) ⇒ Object
Verify challenge-bound nonce in memo.
-
.verify_server(memo, server_id) ⇒ Object
Verify server fingerprint in memo.
Class Method Details
.decode(memo) ⇒ Object
Decode an MPP attribution memo.
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/mpp/methods/tempo/attribution.rb', line 94 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
38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/mpp/methods/tempo/attribution.rb', line 38 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
26 27 28 |
# File 'lib/mpp/methods/tempo/attribution.rb', line 26 def fingerprint(value) keccak256(value.encode(Encoding::UTF_8))[0, 10] end |
.keccak256(data) ⇒ Object
Ethereum Keccak-256. SHA3-256 is not a substitute (different padding).
17 18 19 |
# File 'lib/mpp/methods/tempo/attribution.rb', line 17 def keccak256(data) Digest::Keccak.digest(data, 256) end |
.mpp_memo?(memo) ⇒ Boolean
Check if a memo is an MPP attribution memo.
53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/mpp/methods/tempo/attribution.rb', line 53 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 |
.tag ⇒ Object
Compute TAG = keccak256("mpp")[0:4]
22 23 24 |
# File 'lib/mpp/methods/tempo/attribution.rb', line 22 def tag @tag ||= keccak256("mpp".b)[0, 4] end |
.verify_challenge_binding(memo, challenge_id) ⇒ Object
Verify challenge-bound nonce in memo.
78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/mpp/methods/tempo/attribution.rb', line 78 def verify_challenge_binding(memo, challenge_id) return false unless challenge_id return false unless mpp_memo?(memo) begin memo_nonce = [memo[52, 14]].pack("H*") rescue ArgumentError return false end memo_nonce == keccak256(challenge_id.encode(Encoding::UTF_8))[0, 7] end |
.verify_server(memo, server_id) ⇒ Object
Verify server fingerprint in memo.
66 67 68 69 70 71 72 73 74 75 |
# File 'lib/mpp/methods/tempo/attribution.rb', line 66 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 |