CommBank ID + income

Design

Contracts, pods, MATTR and the circuit

The documents every component was built against, rendered from the repo at build time.

The income circuit

Private inputs income and salt, public inputs threshold and commitment. Both amounts are range-checked to 32 bits, the commitment is recomputed with Poseidon, and GreaterEqThan(32) does the comparison.

packages/zk-circuits/circuits/income_gte.circomopen in source browser
pragma circom 2.1.6;

// Proves: the prover knows (income, salt) such that
//   commitment == Poseidon(income, salt)   and   income >= threshold
// without revealing income or salt. Amounts are whole AUD per year, 32-bit.
//
// Public signals (in this order): [threshold, commitment]

include "circomlib/circuits/poseidon.circom";
include "circomlib/circuits/comparators.circom";
include "circomlib/circuits/bitify.circom";

template IncomeGte(n) {
    signal input income;      // private
    signal input salt;        // private
    signal input threshold;   // public
    signal input commitment;  // public

    // Range-check both amounts. GreaterEqThan is only sound for n-bit inputs.
    component incomeBits = Num2Bits(n);
    incomeBits.in <== income;
    component thresholdBits = Num2Bits(n);
    thresholdBits.in <== threshold;

    // Binding: the commitment the pod signed must open to this income.
    component h = Poseidon(2);
    h.inputs[0] <== income;
    h.inputs[1] <== salt;
    commitment === h.out;

    // income >= threshold
    component gte = GreaterEqThan(n);
    gte.in[0] <== income;
    gte.in[1] <== threshold;
    gte.out === 1;
}

component main {public [threshold, commitment]} = IncomeGte(32);

The MATTR credential configuration

The mDoc "CommBank ID – Income Verified", with the identity namespace plus the income namespace. income_annualised is net pay.

participants/cba-issuer/mattr/templates/credential-configurations/mdoc/commbank-id-income.yamlopen in source browser
# "CommBank ID – Income Verified": the Commbank ID identity claims (financial-passport) plus the
# CDR-derived income. Issued only through the authorization-code flow, behind the offers gateway.
#   identity  <- cba-issuer claim source (data/customers.json; PF id_token carries only sub)
#   income    <- the same claim source, which merges the pod-runtime income claims (NET income)
branding:
  backgroundColor: '#ffcc00'
  name: CommBank ID – Income Verified
  description: Identity plus income verified from your bank via the Consumer Data Right
claimMappings:
  au.com.cba.commbank_id.1:
    given_name:
      display:
      - locale: en-AU
        name: Given name
      mapFrom: claims.given_name
      required: true
      type: string
    family_name:
      display:
      - locale: en-AU
        name: Family name
      mapFrom: claims.family_name
      required: true
      type: string
    birth_date:
      display:
      - locale: en-AU
        name: Date of birth
      mapFrom: claims.birth_date
      required: false
      type: dateTime
    email:
      display:
      - locale: en-AU
        name: Email
      mapFrom: claims.email
      required: false
      type: string
    resident_country:
      display:
      - locale: en-AU
        name: Country of residence
      mapFrom: claims.resident_country
      defaultValue: AU
      required: false
      type: string
    kyc_level:
      display:
      - locale: en-AU
        name: Identity assurance level
      mapFrom: claims.kyc_level
      required: false
      type: string
    customer_since:
      display:
      - locale: en-AU
        name: Customer since
      mapFrom: claims.customer_since
      required: false
      type: dateTime
    verified_at:
      display:
      - locale: en-AU
        name: Identity verified at
      mapFrom: claims.verified_at
      required: false
      type: dateTime
    age_over_18:
      display:
      - locale: en-AU
        name: Over 18
      mapFrom: claims.age_over_18
      required: false
      type: boolean
  au.com.cba.income.1:
    income_annualised:
      display:
      - locale: en-AU
        name: Annual income (AUD)
      mapFrom: claims.income_annualised
      required: true
      type: number
    income_frequency:
      display:
      - locale: en-AU
        name: Pay frequency
      mapFrom: claims.income_frequency
      required: true
      type: string
    income_employer:
      display:
      - locale: en-AU
        name: Employer
      mapFrom: claims.income_employer
      required: false
      type: string
    income_verified_at:
      display:
      - locale: en-AU
        name: Income verified at
      mapFrom: claims.income_verified_at
      required: true
      type: dateTime
    income_source:
      display:
      - locale: en-AU
        name: Income source
      mapFrom: claims.income_source
      defaultValue: CDR
      required: true
      type: string
    income_commitment:
      display:
      - locale: en-AU
        name: Income commitment
      mapFrom: claims.income_commitment
      required: true
      type: string
claimSourceId: ${CLAIM_SOURCE_ID_CBA_ISSUER}
expiresIn:
  months: 3
includeStatus: true
type: au.com.cba.commbank-id-income
participants/cba-issuer/mattr/templates/claim-sources/cba-issuer-claims.yamlopen in source browser
# Claim source for "CommBank ID – Income Verified". MATTR calls it at credential issuance (when
# the wallet retrieves the credential, not at offer time):
#   GET ${OFFERS_API_URL}/claims/mattr?sub=<CBA sub>&purpose=credential-issuance
#   x-api-key: <INTERNAL_API_KEY>   (MATTR sends api-key auth as x-api-key; CONTRACTS v1.1 #6)
# The offers API answers with the customer's identity (data/customers.json - CBA PF's id_token
# carries only sub) merged with the pod's flattened income claims (pod-runtime /claims/mattr,
# purpose credential-issuance). Fields land at claims.*: given_name, family_name, birth_date,
# email, resident_country, kyc_level, verified_at, age_over_18, income_annualised (NET),
# income_frequency, income_employer, income_verified_at, income_source, income_commitment.
# No defaultValue on sub: a missing sub must fail issuance, never fetch someone else's data.
# OFFERS_API_URL must be reachable from MATTR (public HTTPS).
name: cba-issuer-claims
url: ${OFFERS_API_URL}/claims/mattr
requestMethod: GET
requestParameters:
  sub:
    mapFrom: claims.sub
  purpose:
    defaultValue: credential-issuance
authorization:
  type: api-key
  value: '${INTERNAL_API_KEY}'