Module: Mpp::Methods::Tempo::Proof
- Defined in:
- lib/mpp/methods/tempo/proof.rb
Overview
EIP-712 proof credentials for zero-amount challenges.
Domain: { name: “MPP”, version: “1”, chainId } Types: { Proof: [{ name: “challengeId”, type: “string” }] } Message: { challengeId: <challenge.id> }
Constant Summary collapse
- DOMAIN_NAME =
"MPP"- DOMAIN_VERSION =
"1"- DOMAIN_TYPE_HASH =
EIP-712 domain separator type hash
"EIP712Domain(string name,string version,uint256 chainId)"- PROOF_TYPE_HASH =
"Proof(string challengeId)"
Class Method Summary collapse
-
.abi_encode(*values) ⇒ Object
ABI-encode values (packed 32-byte words).
-
.domain_separator(chain_id) ⇒ Object
Compute the EIP-712 domain separator.
- .keccak256(data) ⇒ Object
-
.parse_source(source_str) ⇒ Object
Parse a proof source DID.
- .recover_address(hash, sig_bytes) ⇒ Object
-
.sign(account:, chain_id:, challenge_id:) ⇒ Object
Sign a proof credential (client-side).
-
.signing_hash(chain_id:, challenge_id:) ⇒ Object
Compute the full EIP-712 signing hash.
-
.source(address:, chain_id:) ⇒ Object
Construct source DID for proof credentials.
-
.struct_hash(challenge_id) ⇒ Object
Compute the EIP-712 struct hash for Proof(challengeId).
- .uint256(value) ⇒ Object
-
.verify(address:, chain_id:, challenge_id:, signature:) ⇒ Object
Verify a proof credential signature (server-side).
Class Method Details
.abi_encode(*values) ⇒ Object
ABI-encode values (packed 32-byte words).
97 98 99 100 101 102 103 104 105 106 |
# File 'lib/mpp/methods/tempo/proof.rb', line 97 def abi_encode(*values) values.map { |v| case v when String v.b.rjust(32, "\x00".b) when Integer uint256(v) end }.join end |
.domain_separator(chain_id) ⇒ Object
Compute the EIP-712 domain separator.
28 29 30 31 32 33 34 35 36 37 |
# File 'lib/mpp/methods/tempo/proof.rb', line 28 def domain_separator(chain_id) keccak256( abi_encode( keccak256(DOMAIN_TYPE_HASH), keccak256(DOMAIN_NAME), keccak256(DOMAIN_VERSION), uint256(chain_id) ) ) end |
.keccak256(data) ⇒ Object
22 23 24 25 |
# File 'lib/mpp/methods/tempo/proof.rb', line 22 def keccak256(data) Kernel.require "eth" Eth::Util.keccak256(data) end |
.parse_source(source_str) ⇒ Object
Parse a proof source DID. Returns { address:, chain_id: } or nil.
83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/mpp/methods/tempo/proof.rb', line 83 def parse_source(source_str) match = source_str.match(/\Adid:pkh:eip155:(0|[1-9]\d*):(.+)\z/) return nil unless match chain_id = Integer(match[1]) address = match[2] return nil unless address.match?(/\A0x[a-fA-F0-9]{40}\z/) {address: address, chain_id: chain_id} rescue ArgumentError nil end |
.recover_address(hash, sig_bytes) ⇒ Object
112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/mpp/methods/tempo/proof.rb', line 112 def recover_address(hash, sig_bytes) Kernel.require "eth" return nil unless sig_bytes.bytesize == 65 sig_hex = "0x#{sig_bytes.unpack1("H*")}" # Use raw ecrecover (not personal_recover which adds EIP-191 prefix) recovered_key = Eth::Signature.recover(hash, sig_hex) Eth::Util.public_key_to_address(recovered_key).to_s rescue => _e nil end |
.sign(account:, chain_id:, challenge_id:) ⇒ Object
Sign a proof credential (client-side).
57 58 59 60 61 |
# File 'lib/mpp/methods/tempo/proof.rb', line 57 def sign(account:, chain_id:, challenge_id:) hash = signing_hash(chain_id: chain_id, challenge_id: challenge_id) sig = account.sign_hash(hash) "0x#{sig.unpack1("H*")}" end |
.signing_hash(chain_id:, challenge_id:) ⇒ Object
Compute the full EIP-712 signing hash.
50 51 52 53 54 |
# File 'lib/mpp/methods/tempo/proof.rb', line 50 def signing_hash(chain_id:, challenge_id:) keccak256( "\x19\x01".b + domain_separator(chain_id) + struct_hash(challenge_id) ) end |
.source(address:, chain_id:) ⇒ Object
Construct source DID for proof credentials.
78 79 80 |
# File 'lib/mpp/methods/tempo/proof.rb', line 78 def source(address:, chain_id:) "did:pkh:eip155:#{chain_id}:#{address}" end |
.struct_hash(challenge_id) ⇒ Object
Compute the EIP-712 struct hash for Proof(challengeId).
40 41 42 43 44 45 46 47 |
# File 'lib/mpp/methods/tempo/proof.rb', line 40 def struct_hash(challenge_id) keccak256( abi_encode( keccak256(PROOF_TYPE_HASH), keccak256(challenge_id) ) ) end |
.uint256(value) ⇒ Object
108 109 110 |
# File 'lib/mpp/methods/tempo/proof.rb', line 108 def uint256(value) [value].pack("Q>").rjust(32, "\x00".b) end |
.verify(address:, chain_id:, challenge_id:, signature:) ⇒ Object
Verify a proof credential signature (server-side).
64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/mpp/methods/tempo/proof.rb', line 64 def verify(address:, chain_id:, challenge_id:, signature:) Kernel.require "eth" hash = signing_hash(chain_id: chain_id, challenge_id: challenge_id) sig_bytes = [signature.delete_prefix("0x")].pack("H*") # Recover the signer address from the signature recovered = recover_address(hash, sig_bytes) return false unless recovered recovered.downcase == address.downcase end |