Voucher data processing method
By deploying an elliptic curve accumulator and Bonne-Boyan-Schaqam enhanced anonymous signature on the blockchain, combined with a one-time key, the problem of efficient and reliable use of credential data is solved, realizing lightweight on-chain storage, O(1) revocation and zero-knowledge anonymous verification, which is suitable for credential scenarios requiring privacy protection such as government affairs and finance.
Patent Information
- Application Number
- CN202511716546.1
- Authority / Receiving Office
- CN · China
- Patent Type
- Applications(China)
- Current Assignee / Owner
- Filing Date
- 2025-11-21
- Publication Date
- 2026-02-27
AI Technical Summary
Existing technologies suffer from high computational complexity, large storage overhead, and update delays in the revocable processing of credential data, which can lead to revoked credentials still being verified. Furthermore, storing all credentials on the blockchain results in increased blockchain storage and poor compatibility.
By deploying an elliptic curve accumulator on the blockchain, combined with Bonne-Boyan-Sakam enhanced anonymous signature and one-time key, on-chain lightweight verification and dynamic updating of credential data are achieved. A closed-loop process of generation-verification-revocation-synchronization is designed. The algebraic characteristics of the accumulator are used to support O(1) complexity public lock removal. The blockchain node synchronization mechanism is used to ensure the consistency of the revocation state across the entire network.
It enables efficient and reliable use of credential data, reduces on-chain storage costs, improves credential verification efficiency, avoids leakage of user identity information, and ensures real-time updates of credential status and consistency across the entire network.
Smart Images

Figure CN121585370A_ABST
Abstract
Description
TECHNICAL FIELD
[0001] The present application relates to the technical field of data security, and in particular to a method for processing credential data. BACKGROUND
[0002] In the current digital economy, data credential management is a key technology for ensuring the security and trusted circulation of credentials throughout their life cycle in the digital economy. It is responsible for the entire process management of credentials from generation, storage, verification, update to cancellation, aiming to achieve the goal of "verifiable credentials, controllable data privacy, and efficient and interoperable circulation". Data credential technology is widely used in education, finance, government affairs, medical treatment and other fields, such as education field's academic credential management, financial field's credit qualification credential management, government affairs field's social security participation credential management, medical field's electronic prescription credential management, which can support cross-institution and cross-scene trust transmission. In terms of technology composition, traditional credential management technology takes public key infrastructure (PKI) as the core, combined with symmetric / asymmetric encryption algorithm to ensure credential signature and verification. With the development of blockchain technology, blockchain is becoming a key supporting technology for credential management due to its decentralization, tamper resistance and traceability. It can realize on-chain storage of credential core information and ensure data tamper resistance. At the same time, it breaks down the data barriers between institutions and establishes cross-subject trust without the need for third-party intermediaries, which meets the needs of credential trust management in education, finance and other scenarios. In some existing patents disclosed, for example, the invention patent with the patent name of "Reversible secure signature method for bank-enterprise direct connection scene" (application number CN202510553013.X) provides a technical solution for secure signature using blockchain technology. The patent provides a reversible secure signature method for bank-enterprise direct connection scene. The patent method deeply couples digital signature and encryption in a single logical step, not only reducing the computational complexity, but also meeting the confidentiality, integrity and non-repudiation requirements of information transmission in the financial scene. At the same time, the key update mechanism is designed to ensure that the access to historical and new ciphertexts is blocked after the permission is revoked. To solve the subversion attack that may be launched by internal adversaries, a cryptographic reverse firewall architecture is introduced to reconstruct the encrypted ciphertext through a re-randomization module to block the algorithm risk that may be caused by tampering with the random number generator. Further, blockchain smart contract and aggregate signature technology are introduced to build a decentralized verification platform for bank-enterprise users that is fair, reliable and supports batch verification, improving cloud verification efficiency, and in the event of a major property safety accident, responsibility can also be traced through the records on the blockchain. But there are still bottlenecks in the prior art: in the revocable secure signing and encryption calculation, the certificate revocation list (CRL) is relied on to revoke the certificate, the operation time consumption and storage overhead increase with the number of certificates in O(n), and the update delay easily leads to "revoked certificates still pass the verification", and the full-amount certificate on-chain makes the blockchain block expand, exceeding the storage capacity limit of platforms such as Ethereum, and the compatibility is poor. In summary, in the use technology of certificate data, it is urgent to optimize the revocable certificate processing scheme to improve the efficient and reliable use of revocable certificate data. SUMMARY
[0003] The embodiments of the present application provide a task information processing method to improve the efficient and reliable use of revocable certificate data. In a first aspect, the embodiments of the present application provide a certificate data processing method, comprising: deploying a first elliptic curve accumulator on a blockchain; generating a random number, a public lock, a Boneh-Bellare-Sakai-Enhanced Anonymous Signature, a supervision tag to form a first certificate data according to user attribute data and user public key data uploaded by a user, and generating a content identifier corresponding to the first certificate data and saving it; generating a one-time user public-private key pair on a user side, calling the first elliptic curve accumulator to generate a first proof of the first certificate data, and presenting the first proof; verifying the presented first proof of the first certificate data; calling the first elliptic curve accumulator according to the verified first certificate data, removing and updating the public lock in the first elliptic curve accumulator; and synchronizing the updated first elliptic curve accumulator data in the nodes of the blockchain. Optionally, in the certificate data processing method of the embodiments of the present application, the first elliptic curve accumulator is configured to: based on the secp256r1 elliptic curve, calculate by formula Acc'=Acc·T (wherein T=g^t, t is a random number, g is a generator of the secp256r1 elliptic curve, and T is the public lock) to implement the addition of the first certificate data; and based on the secp256r1 elliptic curve, calculate by formula Acc'=Acc·T^(q-2) (wherein T^(q-2) is the modular inverse of T, q is the order of the secp256r1 elliptic curve, and T is the public lock) to implement the revocation of the first certificate data. Optionally, in the method for processing credential data, the first credential data is formed by generating a random number, a public lock, a Boneh-Bogdanov-Sarkar enhanced anonymous signature, and a supervision tag according to the user attribute data and the user public key data uploaded by the user, which includes: the user submits the user attribute data attr and the user public key data upk; the issuer generates a 256-bit random number t, a public lock T=g^t (g is a generator of a secp256r1 elliptic curve), a Boneh-Bogdanov-Sarkar enhanced anonymous signature σ=BBS+Sign(ask, m) (ask is a private key of the issuer, m=[H1(attr), H1(t), H1(expire), upk.x, upk.y], H1 is a hash-to-elliptic curve function, expire is the expiration time of the credential, upk.x and upk.y are coordinate components of the user public key), and a supervision tag tag=AES-GCM(rpk, t||expire||attrHash||issuerID) (issuerID is an issuer ID, and rpk is a public key of the supervisor); and the first credential data is published on a block chain by a formula add(T, comm=H3(t||attr), tag) emit Issued(T, Acc), wherein t is a 256-bit random number, attr is the user attribute data, tag is the supervision tag, T is the public lock, Acc is the current first elliptic curve accumulator data, comm is the attribute commitment, and H3 is a Merkle hash calculation. Optionally, in the method for processing credential data, the content identifier corresponding to the first credential data is saved, which includes: an encryption key K=KDF (usk, t) is generated, wherein usk is a user private key, t is a 256-bit random number, and KDF is a key derivation function; the Boneh-Bogdanov-Sarkar enhanced anonymous signature, the 256-bit random number, the supervision tag, the attribute commitment, the expiration time of the credential, the user public key, and the public key of the supervisor corresponding to the first credential data are organized in a JSON format, a ciphertext of the first credential data is generated by a formula ciphertext=AES-GCM(K, JSON), the ciphertext is stored in an interstellar file system or a distributed database, a hash value of the ciphertext is taken as the content identifier of the first credential data, and the content identifier is sent to a wallet client of the user for saving. Optionally, in the method for processing credential data, the first proof of the first credential data generated by the first elliptic curve accumulator is called by using a one-time user public-private key pair generated by the user side, and the first proof is presented by: the wallet client of the user generating a one-time user public-private key pair usk_o and upk_o; constructing the public input of zk-SNARK (Groth16): the current first elliptic curve accumulator data Acc, the policy ID, the private input: the BONE-BONE-SARK enhanced BBS+ anonymous signature σ, the 256-bit random number t, the user attribute data attr, and the user private key data usk; circuit constraint 1: BBS+.Verify(σ, m, apk)=1; circuit constraint 2: T=g^t; circuit constraint 3: Acc mod T == 0 (T is a factor of Acc); circuit constraint 4: the range / policy meets the condition, where m=[H1(attr), H1(t), H1(expire), upk.x, upk.y], H1 is a hash-to-elliptic curve function, expire is the expiration time of the credential, and apk is the public key of the issuer, where T is a public lock, and g is the generator of the secp256r1 elliptic curve; using Groth16 to generate a corresponding proof π, and performing two-dimensional code display on the proof π + upk_o + the content identifier corresponding to the first credential data. Optionally, in the method for processing credential data, the first proof of the first credential data presented is verified by: the user submitting the first proof π, the one-time user public key upk_o, and the content identifier corresponding to the first credential data to the verifier, and the verifier calling a verification function verify(π, PID) through a chain contract, where PID is the policy ID identifier; after the verification function returns a determination, it is determined that the first proof of the first credential data is verified. Optionally, in the method for processing credential data, the first elliptic curve accumulator is called according to the verified first credential data, and the public lock in the updated first elliptic curve accumulator is removed by: the issuer or the supervisor obtaining the random number t of the verified first credential data, calculating Tinv=T^(q-2), where T^(q-2) is the modular inverse of T, q is the order of the secp256r1 elliptic curve, T is the public lock T=g^t, and g is the generator of the secp256r1 elliptic curve; calling the chain contract remove(T, Tinv) related to the first elliptic curve accumulator to update the first elliptic curve accumulator Acc=Acc·Tinv, and marking revoked[hash(T)] as true. Optionally, in the credential data processing method of the embodiment of the present application, further comprising: iterating the user key usk according to a first configurable period, the iteration formula being usk_i=KDF(usk_{i-1}, epoch_i) (KDF is a key derivation function, and epoch_i is a current iteration period identifier), and the corresponding public key pk_i being g^usk_i, g being a generator of a secp256r1 elliptic curve; and binding the Boneh-Bogunovic-Sarkar enhanced anonymous signature sigma to the pk_i at that time. Optionally, in the credential data processing method of the embodiment of the present application, further comprising: automatically comparing the 'local cache Acc / epoch' and 'the latest Acc / epoch on the blockchain' when the user wallet client is connected to the network, wherein epoch is a current iteration period identifier, and Acc is current first elliptic curve accumulator data; if the epoch is updated, a new usk_i and pk_i are automatically generated; and if the Acc is updated, the corresponding proof pi is generated again using Groth16. Optionally, in the credential data processing method of the embodiment of the present application, further comprising: using a private key rsk of a supervisor to decrypt the tag, obtaining t, and then calculating T=g^t, retrieving the user information of the corresponding first credential data through the Issued event triggered on the chain when issuing, and realizing supervision tracking, wherein the tag is a supervision tag, t is a random number, and g is a generator of a secp256r1 elliptic curve. The technical scheme of the present application is adopted, the elliptic curve accumulator is deployed on the blockchain, so as to realize the on-chain lightweight verification and dynamic update of the effectiveness of the credential data, solve the efficiency bottleneck caused by the full storage of the traditional credential system, ensure the anonymity of the credential data by combining the Boneh-Bogunovic-Sarkar enhanced anonymous signature, avoid the leakage of user identity information, and through the design of the closed loop process of 'generation-verification-revocation-synchronization', support the public lock removal with O(1) complexity by using the algebraic characteristics of the accumulator, and cooperate with the synchronization mechanism of the blockchain node to ensure the global consistency of the revocation state. In this way, an anonymous credential system supporting efficient revocation can be constructed, and the efficient and reliable use level of the revocable credential data is greatly improved. BRIEF DESCRIPTION OF DRAWINGS
[0004] In order to more clearly illustrate the technical solutions in the embodiments of the present application, the following will briefly introduce the drawings needed to be used in the embodiment description. Obviously, the drawings in the following description are some embodiments of the present application, and other drawings can also be obtained by those skilled in the art without any creative labor. Figure 1 A flowchart of a credential data processing method provided for the first embodiment of the present application; Figure 2A service architecture diagram of the voucher data processing system for the second embodiment of the present application is provided. Figure 3 A key rotation processing flowchart in the voucher data processing system for the third embodiment of the present application is provided. DETAILED DESCRIPTION
[0005] In order to make the objects, technical solutions and advantages of the embodiments of the present application clearer, the technical solutions in the embodiments of the present application will be described clearly and completely below with reference to the drawings in the embodiments of the present application. Obviously, the described embodiments are only some of the embodiments of the present application, but not all the embodiments of the present application. Based on the embodiments in the present application, all other embodiments obtained by those of ordinary skill in the art without creative work fall within the scope of protection of the present application. The terms used in the embodiments of the present application are only for the purpose of describing the specific embodiments, and are not intended to limit the present application. The singular forms "a", "an" and "the" used in the embodiments of the present application and the appended claims are also intended to include the plural forms, unless the context clearly indicates otherwise. "Plural" generally includes at least two. Depending on the context, the word "if" as used herein can be interpreted to mean "when" or "while" or "in response to determining" or "in response to detecting". Similarly, depending on the context, the phrase "if determined" or "if detecting (a stated condition or event)" can be interpreted to mean "when determined" or "in response to determining" or "when detecting (a stated condition or event)" or "in response to detecting (a stated condition or event)". In addition, the step sequence in each of the following method embodiments is only an example, and is not strictly limited. In the embodiment of the present application, through the three-layer technical fusion of "elliptic curve accumulator + Boneh-Boyen-Shacham enhanced anonymous signature (BBS+ signature) + one-time key", an anonymous credential system supporting efficient revocation is constructed: first, the elliptic curve accumulator is deployed on the blockchain to realize on-chain lightweight verification and dynamic update of credential validity, solving the efficiency bottleneck caused by full storage of traditional credential systems; second, combining BBS+ signature and one-time key, while ensuring the anonymity of credentials, the selective disclosure of attributes is realized through zero-knowledge proof, avoiding the leakage of user identity information; third, a closed-loop process of "generation-verification-revocation-synchronization" is designed, and the algebraic property of the accumulator is used to support O(1) complexity of public lock removal, and the blockchain node synchronization mechanism is used to ensure the consistency of the revocation state in the whole network. The technical effects of the embodiment of the present application are remarkable: the elliptic curve accumulator greatly reduces the on-chain storage cost, so that the credential verification does not need to traverse the full data, improving the system efficiency; the combination of BBS+ signature and one-time key realizes "anonymous presentation but verifiable legitimacy", protecting user privacy; the dynamic revocation and node synchronization mechanism ensures real-time update of the credential state, solving the revocation lag problem of traditional systems; the tamper-proof nature of the blockchain provides a trusted basis for the whole process, balancing anonymity, efficiency and compliance, and is suitable for credential scenarios such as government affairs and finance that require privacy protection. Embodiment One Figure 1 A flowchart of a credential data processing method provided for the first embodiment of the present application is shown. The first embodiment takes "university education anonymous credentials" as a specific application scenario, and realizes the whole life cycle processing of credential data based on a blockchain platform (compatible with Ant Chain and Ethereum technical specifications) to realize the core design of "on-chain lightweight storage, O(1) revocation, zero-knowledge anonymous verification, and supervision controllable tracking". The following will disclose each step in detail according to the process of "deploying accumulators--generating credentials--generating proofs--verifying proofs--updating accumulators--node synchronization". Step 100: Deploying a first elliptic curve accumulator on the blockchain In this embodiment, the first elliptic curve accumulator is the core component of credential validity verification and efficient revocation, which needs to be deployed as a smart contract based on the billion block chain main chain (L1), and at the same time, the initialization configuration is completed. The core goal of this step is to build a "lightweight state carrier on the chain", which only stores the core data required for credential validity verification (single credential occupies ≤256 bytes, meeting the "on-chain storage function" requirement), avoiding the problem of block expansion caused by storing full credential data on the chain. At the same time, through contract initialization, the accumulator supports O(1) time complexity of addition / revocation operation, and has the ability to resist rollback of blockchain forks. The blockchain platform selected in the present embodiment is version 1.5 (compatible with Ethereum Virtual Machine, supporting Solidity 0.8.17 contract development), which has a consensus mechanism of “Proof of Stake (PoS) + Practical Byzantine Fault Tolerance (PBFT) ” hybrid consensus, a block generation time of about 3 seconds, a single-block Gas upper limit of 12 million, and can meet the rapid on-chain demand of the accumulator update transaction; at the same time, it supports the pre-compiled “Elliptic Curve Modular Exponentiation Function” (address: 0x0000000000000000000000000000000000000005), which can efficiently calculate the point multiplication and modular inverse operation of the secp256r1 curve. The elliptic curve parameters in the present embodiment are as follows (in accordance with NIST FIPS 186-4 standard): curve equation: y^2 = x^3 + ax + b, where a = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 - 3, b = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b; Curve order q: 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 (256-bit prime number, ensuring the security of random number t); generator g: coordinates (g_x, g_y), where g_x = 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296, g_y = 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5 (the generator g is the basis of the elliptic curve group, used to calculate the public lock T = g^t). The accumulator contract core parameters in the present embodiment need to be configured with the following parameters (stored in the contract state variable) during initialization: Initial accumulator value Acc_{init}: The identity element of the elliptic curve group (point at infinity, identified by bytes32(0) in the contract, but equivalent to "multiplicative identity 1" in actual calculation); Regulatory public key RPK: The secp256r1 public key of the education regulatory department, with coordinates (rpk_x = 0x7a2f4d8c9e3b5a7d1f2c4e6b8d0a1c3e5f7b9d1a2c4e6f8a0b1d3c5e7f9a0b1c, rpk_y = 0x8b3e5f7a9d1c3e5b7f9a1c3d5e7f9b1d3c5e7a9f1b3d5c7e9a1b3f5d7c9e1a2b) (used to generate regulatory tags); The revoked anti-rollback mapping is of type mapping(bytes32 => bool), with the key being the SHA-256 hash of the public lock T and the value being a boolean (true indicates revoked, false indicates valid). It is used to prevent the "revoked certificate from being revived" due to a blockchain fork rollback. Issuer public key list issuerApks: Stores the BBS + signature public key apk of the legitimate issuer (such as "XX University"), in the format address => bytes (ensuring that only authorized issuers can generate valid credentials). In this embodiment, to deploy the first elliptic curve accumulator, the contract "AccumulatorManager.sol" is written using Solidity 0.8.17. The core functions include initialize() (initialization), add() (adding a public lock), remove() (removing the public lock), and getCurrentAcc() (getting the current accumulator value). The following is a key code snippet: / / Introduce the secp256r1 precompiled contract interface interface ISecp256r1 { function multiply(bytes calldata point, uint256 scalar) externalreturns (bytes memory result); / / dot multiply: point^scalar function invert(bytes calldata point) external returns (bytesmemory result); / / Modular inversion: point^(q-2)} contract AccumulatorManager { ISecp256r1 public constant secp256r1 = ISecp256r1(0x0000000000000000000000000000000000000005); bytes public currentAcc; / / Current accumulator value (64 bytes: x coordinate 32 bytes + y coordinate 32 bytes) bytes public rpk; / / Regulatory public key (64 bytes) mapping(bytes32 => bool) public revoked; / / Revoked public lock mapping mapping(address => bytes) public issuerApks; / / Issuer BBS+ public key list / / Initialization function (called only once) function initialize(bytes calldata _rpk, mapping(address =>bytes) calldata _issuerApks) external { require(currentAcc.length == 0, "Already initialized"); / / Initial accumulator set to identity element (64 bytes all 0, representing infinity point) currentAcc = bytes64(0); / / Configure regulatory public key (verify format: 64 bytes, conform to secp256r1 public key specification) require(_rpk.length == 64, "RPK must be 64 bytes"); rpk = _rpk; / / Configure issuer public key list for (uint256 i = 0; i < _issuerApks.length; i++) { address issuer = _issuerApks[i].key; bytes memory apk = _issuerApks[i].value; require(apk.length == 64, "APK must be 64 bytes"); issuerApks[issuer] = apk;}} / / Add a public lock T: Acc' = Acc · T function add(bytes calldata T) external returns (bytes memorynewAcc) { bytes32 THash = keccak256(T); require(!revoked[THash], "T has been revoked"); / / Prevent adding a revoked T if (currentAcc == bytes64(0)) { / / Initial state: Acc = T newAcc = T; } else { / / Call the pre-compiled contract to calculate Acc · T (dot product) newAcc = secp256r1.multiply(currentAcc, T);} currentAcc = newAcc; emit TAdded(T, newAcc); / / Trigger an event for monitoring retrieval return newAcc;} / / Remove public lock T: Acc' = Acc · T^(q-2) function remove(bytes calldata T, bytes calldata Tinv) externalreturns (bytes memory newAcc) { bytes32 THash = keccak256(T); require(!revoked[THash], "T already revoked"); / / Verify whether Tinv is the modular inverse of T (T · Tinv = identity element) bytes memory product = secp256r1.multiply(T, Tinv); require(product == bytes64(0), "Invalid Tinv"); / / Product is in units, validation passed / / Calculate the new Acc = Acc · Tinv newAcc = secp256r1.multiply(currentAcc, Tinv); currentAcc = newAcc; revoked[THash] = true; / / Marks T as revoked emit TRemoved(T, newAcc); / / Trigger the undo event return newAcc;}} The contract is compiled using the Truffle framework to generate an ABI file (AccumulatorManager.abi) and bytecode (AccumulatorManager.bin). The compilation parameters are set to "optimization level 200" to ensure that the gas cost of modular exponentiation is minimized. In this embodiment, contract deployment and initialization are performed as follows. Deployment Account Preparation: Use a blockchain testnet account (address: 0x1234567890abcdef1234567890abcdef12345678) with a balance of ≥1000 EGC (E-Blockchain native token) to pay for deployment gas fees (estimated deployment gas is approximately 3 million, with a fee of approximately 0.3 EGC). Invoking a deployment transaction: Send a deployment transaction via Web3.js 1.10.0. An example code snippet is shown below. const Web3 = require('web3'); const web3 = new Web3('https: / / testnet.yejieblockchain.com:8545'); / / Testnet RPC address const account = '0x1234567890abcdef1234567890abcdef12345678'; const privateKey = '0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcd'; / / Read the contract ABI and bytecode const abi = require('. / AccumulatorManager.abi.json'); const bytecode = require('. / AccumulatorManager.bin.json'); / / Construct the contract instance const AccumulatorManager = new web3.eth.Contract(abi); / / Send the deployment transaction async function deployContract() { const nonce = await web3.eth.getTransactionCount(account); const tx = { from: account, nonce: nonce, data: bytecode, gas: 3000000, / / Estimated gas gasPrice: await web3.eth.getGasPrice() }; / / Sign the transaction const signedTx = await web3.eth.accounts.signTransaction(tx, privateKey); / / Send the transaction const txReceipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction); console.log('Contract deployment address:', txReceipt.contractAddress); / / Output: 0x9876543210fedcba9876543210fedcba98765432} deployContract(); Contract initialization: After successful deployment, call the `initialize()` function to complete parameter configuration, passing in the regulatory public key `_rpk` (64 bytes) and the list of issuer public keys `_issuerApks`. The estimated transaction gas is 1 million. Code example: async function initializeContract() { const contract = new web3.eth.Contract(abi, '0x9876543210fedcba9876543210fedcba98765432'); const rpk = '0x7a2f4d8c9e3b5a7d1f2c4e6b8d0a1c3e5f7b9d1a2c4e6f8a0b1d3c5e7f9a0b1c8b3e5f7a9d1c3e5b7f9a1c3d5e7f9b1d3c5e7a9f1b3d5c7e9a1b3f5d7c9e1a2b'; / / 64-byte supervisory public key const issuerApks = [ { key: '0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b', value: '0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c2964fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5'} / / XX University's BBS + Public Key ]; const tx = await contract.methods.initialize(rpk, issuerApks).send({ from: account, gas: 1,000,000 gasPrice: await web3.eth.getGasPrice()}); console.log('Initialization result:', tx.status); / / Output: true (success) initializeContract(); Step 101: Based on the user attribute data and user public key data uploaded by the user, the issuer generates a random number, a public lock, a Boneh-Bogun-Sarkar enhanced anonymous signature, a regulatory tag to form the first credential data, and generates and saves the content identifier corresponding to the first credential data In this embodiment, preferably, the first credential data formed by the issuer based on the user attribute data and user public key data uploaded by the user includes: the user submits user attribute data attr and user public key data upk; the issuer generates a 256-bit random number t, a public lock T=g^t (g is the generator of the secp256r1 elliptic curve), a Boneh-Bogun-Sarkar enhanced anonymous signature σ=BBS+Sign(ask, m) (ask is the private key of the issuer, m=[H1(attr), H1(t), H1(expire), upk.x, upk.y], H1 is a hash to elliptic curve function, expire is the expiration time of the credential, upk.x and upk.y are coordinate components of the user public key), a regulatory tag tag=AES-GCM(rpk, t||expire||attrHash||issuerID) (issuerID is the issuer ID, rpk is the public key of the regulatory party); on the blockchain, through the formula add(T, comm=H3(t||attr), tag) emitIssued(T, Acc), where t is a 256-bit random number, attr is user attribute data, tag is a regulatory tag, T is the public lock, Acc is the current first elliptic curve accumulator data, comm is the attribute commitment, H3 is a Merkle hash calculation, to publish the first credential data. In this embodiment, the first credential data is a "college education credential", which is generated by the issuer (XX University) based on the attributes and public keys submitted by the user (graduate Zhang San), and contains four core components: "random number t, public lock T, BBS+signature σ, regulatory tag tag". At the same time, a content identifier (CID) is generated through off-chain encryption storage to realize the architecture of "on-chain lightweight storage + off-chain large-capacity storage". The core goal of this step is to generate a "verifiable, revocable, and manageable" diploma certificate data without exposing user privacy (such as name, student ID), and to reduce on-chain costs through off-chain storage. Specifically, it needs to meet the following requirements: a random number t uniquely corresponds to a single certificate (as a "revocation key"); BBS + signature σ binds user attributes and public key, ensuring that the certificate cannot be tampered with; the supervision tag tag can only be decrypted by the supervisor, realizing "controllable anonymity"; the Content Identifier (CID) uniquely corresponds to the certificate ciphertext, supporting integrity verification in subsequent verification. The following discloses the technical parameters in this step. (1) User attributes and key parameters User attribute data attr: structured data, including "Name: Zhang San, Student ID: 2021001001, Major: Computer Science and Technology, Graduation Year: 2025, Education Level: Bachelor, Graduation University: XX University" (a total of 6 fields, which need to be protected by hash mapping to protect privacy); User long-term key pair: generated based on secp256r1, private key usk = 0x2c4e6f8a0b1d3c5e7f9a0b1c3d5e7f9a0b1c3d5e7f9a0b1c3d5e7f9a0b1c3d5e7f9a0b1c3d5e, public key upk = (upk_x, upk_y), where upk_x = 0x3d5e7f9a0b1c3d5e7f9a0b1c3d5e7f9a0b1c3d5e7f9a0b1c3d5e7f9a0b1c3d5e7f9a0b1c3d5e, upk_y = 0x5f7a9d1c3e5b7f9a1c3d5e7f9b1d3c5e7a9f1b3d5c7e9a1b3f5d7c9e1a2b3c4d; Certificate expiration time expire: set to "20991231" (format: YYYYMMDD), indicating that the certificate is valid for a long time (unless actively revoked). (2) Password algorithm selection Random number generation: use a "operating system random number + hardware entropy source" hybrid scheme to generate a 256-bit random number t through crypto.getRandomValues() (browser side) or os.urandom() (server side), ensuring unpredictability; Hash function: H1 is a "SHA-256 Hash + Elliptic Curve Mapping" function that maps data of any length to a point on the secp256r1 curve; H3 is a Merkle hash function (SHA-256) used to calculate the attribute commitment comm = H3(t||attr); Symmetric encryption: AES-GCM-256 (compliant with NIST SP 800-38D) is used to generate the credential ciphertext and the supervision tag tag, with an initial vector IV of 12 bytes of random number and an authentication tag length of 128 bits; Key derivation function: HKDF-SHA256 (compliant with RFC 5869) is used to generate the encryption key K = KDF(usk,t); Off-chain storage: IPFS version 0.18.1, node address " / ip4 / 192.168.1.100 / tcp / 4001 / p2p / QmXf8zL5a7b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f", used to store the credential ciphertext. The following gives the disclosure of the four sub-steps of this step: "User submits data--->Issuer generates credential components--->On-chain publishes credentials--->Off-chain stores CID". Sub-step 1: User submits attributes and public key The user submits data through the "XX University Graduate Service Platform" (Web) as follows: The user logs in to the platform and enters the "Educational Credential Application" page; The platform front end generates a user long-term key pair (usk, upk) through JavaScript, where usk is stored in the user's local wallet (such as the MetaMask plugin, encrypted storage, and key protected by user password), and upk is submitted to the platform backend; The user fills in the attribute data attr (name, student ID, etc.), and the front end performs preliminary verification on attr (such as the student ID format "2021XXXXXX"), and after verification, attr and upk are submitted to the issuer backend server (XX University Information Center, IP: 10.0.0.10, port: 443) through HTTPS (TLS 1.3); The issuer backend receives the data, records the submission time (2025-06-20 10:00:00), and generates an application number (AP202506200001) for subsequent tracking. Sub-step 2: Issuer generates credential core components Issuer (XX University) backend server (using Java Spring Boot framework) based on the received data, generate random number t, public lock T, BBS + signature sigma, regulatory label tag, the specific process is as follows: (1) Generate 256bit random number t, the generated t needs to be stored in the issuer database (MySQL 8.0, encrypted field, using AES-256 encryption storage), associated with the application number AP202506200001, used for subsequent revocation operation. Preferably, a 256bit random number can be generated through the Java SecureRandom class. (2) Calculate public lock T = g^t Based on the point multiplication operation of secp256r1 elliptic curve, the generator g and the random number t are multiplied to get the public lock T (elliptic curve point). Preferably, the Bouncy Castle 1.72 encryption library is used to calculate the public lock. The calculated T needs to be converted to 64 byte format (T_x 32 bytes + T_y 32 bytes) for subsequent on-chain addition and proof generation. (3) Generate BBS+ anonymous signature sigma BBS+ signature (Boneh-Bogoliubov-Sarkar enhanced signature) supports "multi-message signature + anonymous presentation", and the issuer signs the vector m composed of "attribute hash, random number hash, expiration time hash, user public key" based on the private key ask, the specific process is as follows: Construct message vector: m_1 = H1(attr) : Map the attribute attr to a point on the secp256r1 curve through the H1 function, code example: public static ECPoint H1(byte[] data) { / / First step: SHA-256 hash MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); byte[] hash = sha256.digest(data); / / Second step: Map the hash value to an elliptic curve point (using "try method") BigInteger x = new BigInteger(1, hash); while (true) { try { / / Calculate y^2 = x^3 + ax + b mod q BigInteger ySquared = x.modPow(BigInteger.valueOf(3), q) .add(a.multiply(x).mod(q)) .add(b).mod(q); BigInteger y = ySquared.modPow(q.add(BigInteger.ONE).divide(BigInteger.valueOf(4)), q); if (y.modPow(BigInteger.valueOf(2), q).equals(ySquared)){ return ecParams.getCurve().createPoint(x, y); } x = x.add(BigInteger.ONE); } catch (Exception e) { x = x.add(BigInteger.ONE);}}} / / Construct the attr byte array (JSON format) String attrJson = "{\"Name\":\"Zhang San\",\"Student ID\":\"2021001001\",\"Major\":\"Computer Science and Technology\",\"Graduation Year\":\"2025\",\"Education Level\":\"Undergraduate\",\"University\":\"XX University\"}"; byte[] attrBytes = attrJson.getBytes(StandardCharsets.UTF_8); ECPoint m1 = H1(attrBytes); The following are the other parameters of the message vector: m_2 = H1(t): Maps the random number t to points on an elliptic curve; m_3 = H1(expire): Maps the byte array of the expiration time "20991231" to elliptic curve points; m_4 = upk_x : x coordinate of the user public key (BigInteger type); m_5 = upk_y : y coordinate of the user public key (BigInteger type); The final message vector m = [m_1, m_2, m_3, m_4, m_5]. Then generate BBS + signature, the technical scheme is implemented as follows: The issuer private key ask = 0x3e5f7a9d1c3e5b7f9a1c3d5e7f9b1d3c5e7a9f1b3d5c7e9a1b3f5d7c9e1a2b3c (corresponding public key apk = g^ask, has been registered to the chain contract issuerApks), using Bouncy Castle to realize signature, the code example is as follows: public class BBSPlusSigner { private static final SecureRandom secureRandom = new SecureRandom(); / / BBS+ signature: σ = (R, s) public static Tuple<ECPoint, BigInteger> sign(BigInteger ask,ECPoint[] m) { / / 1. Generate random number r BigInteger r = new BigInteger(q.bitLength(), secureRandom).mod(q); / / 2. Calculate R = g^r · ∏(m_i^r) (simplified version, actual calculation needs to be calculated according to BBS+ standard) ECPoint R = g.multiply(r).normalize(); for (ECPoint mi : m) { R = R.add(mi.multiply(r)).normalize();} / / 3. Calculate challenge value c = H2(R || apk || m) byte[] RBytes = R.getEncoded(false); / / 65 bytes (compressed format) ECPoint apk = g.multiply(ask).normalize(); byte[] apkBytes = apk.getEncoded(false); / / 65 bytes byte[] mBytes = new byte[RBytes.length + apkBytes.length +m.length * 64]; System.arraycopy(RBytes, 0, mBytes, 0, RBytes.length); System.arraycopy(apkBytes, 0, mBytes, RBytes.length,apkBytes.length); for (int i = 0; i < m.length; i++) { byte[] miBytes = m[i].getEncoded(false); System.arraycopy(miBytes, 1, mBytes, RBytes.length +apkBytes.length + i * 64, 64); / / Remove the compression flag bit } MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); byte[] cBytes = sha256.digest(mBytes); BigInteger c = new BigInteger(1, cBytes).mod(q); / / 4. Calculate s = r + c · ask mod q BigInteger s = r.add(c.multiply(ask)).mod(q); / / Return the signature σ = (R, s) return new Tuple<>(R, s); } public static void main(String[] args) { BigInteger ask = new BigInteger("3e5f7a9d1c3e5b7f9a1c3d5e7f9b1d3c5e7a9f1b3d5c7e9a1b3f5d7c9e1a2b3c", 16); ECPoint[] m = {m1, m2, m3, ecParams.getCurve().createPoint(m4, BigInteger.ZERO), ecParams.getCurve().createPoint(m5,BigInteger.ZERO)}; / / Simplified example Tuple<ECPoint, BigInteger> sigma = sign(ask, m); System.out.println("BBS+signature R_x:" + sigma.getKey().getAffineXCoord().toBigInteger().toString(16)); System.out.println("BBS+signatures:" + sigma.getValue().toString(16));}} The generated signature σ = (R, s) needs to be stored in byte format (R 65 bytes + s 32 bytes = 97 bytes) for subsequent proof generation and verification. Next, regulatory tags are generated, the technical details of which are as follows. The regulatory tag is the core of "regulatory controllable tracking." It uses AES-GCM-256 to encrypt concatenated data consisting of "random number t, expiration time expire, attribute hash attrHash, and issuer ID." The encryption key is derived from the regulatory public key rpk. The specific process is as follows: Construct the data to be encrypted: attrHash = H3(attr): Merkle hash (SHA-256) of attribute attr. Code example: public static byte[] H3(byte[] data) { MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); return sha256.digest(data);} byte[] attrHash = H3(attrBytes); / / 32 bytes issuerID = "XXXXXXXXX" (university code assigned by the education part), converted to byte array (12 bytes); plaintext = t || expireBytes || attrHash || issuerIDBytes, where expireBytes = "20991231".getBytes(StandardCharsets.UTF_8) (8 bytes), total length is 32 + 8 + 32 + 12 = 84 bytes. Then derive the AES encryption key, the technical details are as follows. Derive a 32-byte AES key from the x-coordinate of the regulatory public key rpk, code example: public static byte[] deriveAesKey(ECPoint rpk) { BigInteger rpkX = rpk.getAffineXCoord().toBigInteger(); byte[] rpkXBytes = rpkX.toByteArray(); / / Take the last 32 bytes as the HKDF salt value byte[] salt = Arrays.copyOfRange(rpkXBytes, rpkXBytes.length -32, rpkXBytes.length); / / HKDF-SHA256 derives the AES key (information field is "RegulatoryTagKey") HKDF hkdf = new HKDF(new SHA256Digest()); hkdf.init(salt, "RegulatoryTagKey".getBytes(StandardCharsets.UTF_8)); byte[] aesKey = new byte
[32] ; hkdf.generateBytes(aesKey, 0, 32); return aesKey;} ECPoint rpk = createECPoint( "7a2f4d8c9e3b5a7d1f2c4e6b8d0a1c3e5f7b9d1a2c4e6f8a0b1d3c5e7f9a0b1c", "8b3e5f7a9d1c3e5b7f9a1c3d5e7f9b1d3c5e7a9f1b3d5c7e9a1b3f5d7c9e1a2b"); byte[] aesKey = deriveAesKey(rpk); / / 32-byte AES key. Next, AES-GCM encryption is performed to generate a tag. Example code is shown below: public static Tuple<byte[], byte[]> encryptAesGcm(byte[] key, byte[] plaintext) { Cipher cipher = Cipher.getInstance("AES / GCM / NoPadding"); SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES"); / / Generate a 12-byte IV byte[] iv = new byte
[12] ; secureRandom.nextBytes(iv); GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128,iv); / / 128-bit authentication tag cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec,gcmParameterSpec); / / Encryption byte[] ciphertext = cipher.doFinal(plaintext); / / Extract the authentication tag (last 16 bytes) byte[] tag = Arrays.copyOfRange(ciphertext, ciphertext.length -16, ciphertext.length); / / Return IV + ciphertext (without tag) byte[] ivCiphertext = Arrays.copyOf(ciphertext, ciphertext.length- 16); return new Tuple<>(ivCiphertext, tag);} Tuple<byte[], byte[]> encryptResult = encryptAesGcm(aesKey,plaintext); byte[] ivCiphertext = encryptResult.getKey(); / / 84 + 12 - 16 = 80 bytes (IV 12 bytes + ciphertext 68 bytes) byte[] tag = encryptResult.getValue(); / / 16 bytes / / Final tag storage format: IV (12 bytes) + authentication tag (16 bytes) = 28 bytes byte[] finalTag = new byte[ivCiphertext.length + tag.length]; System.arraycopy(ivCiphertext, 0, finalTag, 0, ivCiphertext.length); System.arraycopy(tag, 0, finalTag, ivCiphertext.length, tag.length); The generated tag is 28 bytes, and only the regulator can derive the AES key with the private key rsk to decrypt and obtain t. Sub-step 3: Publish the credential on the chain The issuer calls the add() function of the AccumulatorManager.sol contract on the chain to publish the public lock T, the attribute commitment comm, and the regulatory tag tag to the blockchain project of the present embodiment, and the specific process is as follows: Compute the commitment: comm = H3(t||attr), that is, SHA-256 hash of "random number t (32 bytes) + attr byte array (about 200 bytes)," code example: byte[] tAttr = new byte[t.length + attrBytes.length]; System.arraycopy(t, 0, tAttr, 0, t.length); System.arraycopy(attrBytes, 0, tAttr, t.length, attrBytes.length); byte[] comm = H3(tAttr); / / 32 bytes Then call the add() function: send the transaction through Web3.js, passing T (64 bytes), comm (32 bytes), and tag (28 bytes) as parameters into the add() function (the contract actually only stores T, and comm and tag are stored through event emit), code example: async function addToBlockchain() { const contract = new web3.eth.Contract(abi, '0x9876543210fedcba9876543210fedcba98765432'); const T = '0x7b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c9d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e'; / / 64 bytes const comm = '0x1f2e3d4c5b6a7f8e9d0c1b2a3f4e5d6c7b8a9f0e1d2c3b4a5f6e7d8c9b0a1f2e'; / / 32 bytes const tag = '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567'; / / 28 bytes const tx = await contract.methods.add(T, comm, tag).send({ from: '0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b', / / Issuing account gas: 500000 gasPrice: await web3.eth.getGasPrice() }); console.log('On-chain published transaction hash:', tx.transactionHash); / / Output: 0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcd console.log('Updated Acc: ', tx.events.TAdded.returnValues.newAcc); / / Output: 0x7b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c9d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e (Same as T, because the initial Acc is the identity element)} addToBlockchain(); Sub-step 4: Generate Content Identifier (CID) for off-chain storage The issuing party organizes "BBS + signature σ, random number t, regulatory tag tag, attribute commitment comm, expiration time expire, user public key upk, and regulatory public key rpk" into JSON format, encrypts it, stores it in IPFS, and generates a unique content identifier (CID). The specific process is as follows: First, construct the credential JSON data: { "sigma": { "R_x": "7b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c", "R_y": "8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e", "s": "4f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a"}, "t": "1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b", "tag": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567", "comm": "1f2e3d4c5b6a7f8e9d0c1b2a3f4e5d6c7b8a9f0e1d2c3b4a5f6e7d8c9b0a1f2e", "expire": "20991231", "upk": { "x": "3d5e7f9a0b1c3d5e7f9a0b1c3d5e7f9a0b1c3d5e7f9a0b1c3d5e7f9a0b1c3d5e", "y": "5f7a9d1c3e5b7f9a1c3d5e7f9b1d3c5e7a9f1b3d5c7e9a1b3f5d7c9e1a2b3c4d"}, "rpk": { "x": "7a2f4d8c9e3b5a7d1f2c4e6b8d0a1c3e5f7b9d1a2c4e6f8a0b1d3c5e7f9a0b1c", "y": "8b3e5f7a9d1c3e5b7f9a1c3d5e7f9b1d3c5e7a9f1b3d5c7e9a1b3f5d7c9e1a2b"}, "issuerID": "XX University_10001", "issueTime": "2025-06-20 10:00:00"} General JSON transcription (jsonBytes, approximately 1500 characters). Next, generate the encryption key: Based on the user's private key usk and a random number t, a 32-byte encryption key K is derived using HKDF-SHA256. Code example: public static byte[] deriveEncryptionKey(byte[] uskBytes, byte[] t){ BigInteger usk = new BigInteger(1, uskBytes); byte[] uskBytesPadded = Arrays.copyOf(usk.toByteArray(), 32); / / Pad 32 bytes / / HKDF initialization (salt value is t, information field is "CredentialEncryptionKey") HKDF hkdf = new HKDF(new SHA256Digest()); hkdf.init(t, "CredentialEncryptionKey".getBytes(StandardCharsets.UTF_8)); byte[] K = new byte
[32] ; hkdf.generateBytes(K, 0, 32); return K; } byte[] uskBytes = Hex.decode("2c4e6f8a0b1d3c5e7f9a0b1c3d5e7f9a0b1c3d5e7f9a0b1c3d5e7f9a0b1c3d5e"); byte[] K = deriveEncryptionKey(uskBytes, t); / / 32-byte encryption key Next, AES-GCM encryption is performed to generate ciphertext, and the technical details are as follows. Encrypting jsonBytes using AES-GCM-256, code example: Tuple<byte[], byte[]> cipherResult = encryptAesGcm(K, jsonBytes); byte[] ivCiphertext = cipherResult.getKey(); / / IV 12 bytes + ciphertext approximately 1500 bytes = 1512 bytes byte[] authTag = cipherResult.getValue(); / / 16 bytes byte[] finalCiphertext = new byte[ivCiphertext.length +authTag.length]; System.arraycopy(ivCiphertext, 0, finalCiphertext, 0,ivCiphertext.length); System.arraycopy(authTag, 0, finalCiphertext, ivCiphertext.length,authTag.length); / / Final ciphertext length: 1512 + 16 = 1528 bytes. Finally, upload to IPFS to generate the CID: To upload encrypted data using the IPFS command-line tool, follow these steps: Save the finalCiphertext as the file credential.cipher; Example output of executing the command `ipfs add credential.cipher`: added QmXf8zL5a7b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f credential.cipher 1528 B / 1528 B [========================================================] 100.00% Finally, the content identifier CID is obtained as follows: CID = QmXf8zL5a7b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f (IPFS CID v0 format, 46 characters). The CID is then returned to the user: the issuer returns the CID to the user's wallet through the "Graduate Service Platform," where the user's wallet stores the CID (linked to the user account) for later use when presenting credentials to the verifier. Step 102: According to the one-time user public-private key pair generated on the user side, call the first elliptic curve accumulator to generate the first proof of the first credential data, and present the first proof In this embodiment, the first proof is a zero-knowledge proof σ based on the Groth16 algorithm, generated by the user's wallet (on the user side), used to prove to the verifier (such as the employer) that the "credential is legal, not revoked, and meets the business policy", while not exposing the user's privacy. The core goal of this step is to complete the zero-knowledge proof generation offline on the user side, ensuring that the verifier can only confirm that the "credential is valid" and cannot obtain the user's identity (such as name, student ID) and credential core data (such as t, σ). Specifically, it needs to meet the following requirements: the proof σ contains "BBS + signature verification, T = g^t, T is an Acc factor, and the business policy meets" four constraints; generate a one-time public-private key pair (usk_o, upk_o) to avoid exposure of the user's long-term public key upk, further protecting privacy; if the user has performed key rotation, the proof needs to bind the latest epoch public key pk_i to ensure forward security. In this embodiment, a wallet client needs to be set up on the user side, which is a browser plugin wallet developed based on Web3.js + React, supporting secp256r1 key generation, Groth16 proof calculation, and IPFS data reading. In this embodiment, the technical details of the implementation of the zero-knowledge proof framework are as follows: Framework selection: gnark v0.9.0 (a zero-knowledge proof framework implemented in Go, supporting Groth16 and Bulletproofs algorithm switching) is used, compiled into browser executable code through WebAssembly (WASM), and integrated into the wallet plugin; Groth16 algorithm needs to be completed in advance Trust setup, jointly executed by the issuer (XX University) and the regulator (education department) "multi-party trusted setup", the generated public parameter file (pk.groth16, vk.groth16) is stored in IPFS (CID: QmZ1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1), and the wallet automatically downloads it to the local when it starts for the first time (about 10MB); Proof calculation overhead: In an Intel Core i7-12700H CPU environment, the Groth16 proof generation time is ≤800ms (including 4 item circuit constraint verification), and the proof size is 288 bytes; Circuit constraint parameters: The circuit input contains "4 public input items + 6 private input items", as follows: Input Type Input Item Data Type / Length Source Public Input Current Accumulator Acc Elliptic Curve Point (64 bytes) Blockchain Contract getCurrentAcc() Public Input Strategy ID (PID) String Hash (32 bytes) Provided by the verifier (e.g. "Education Verification_2025") Public Input Challenge Value challenge 256bit Random Number Wallet Randomly Generated Public Input Issuer Public Key apk Elliptic Curve Point (64 bytes) On-chain Contract issuerApks Private Input BBS + Signature σƒ Struct (97 bytes) After Local Credential Cipher Text Decryption Private Input Random Number t 256bit (32 bytes) After Local Credential Cipher Text Decryption Private Input User Attribute attr ByteArray (about 200 bytes) After Local Credential Cipher Text Decryption Private Input User Long-term Private Key usk_i 256bit (32 bytes) Wallet Encrypted Storage (Current Epoch Key) Private Input Public Lock T Elliptic Curve Point (64 bytes) After Local Credential Cipher Text Decryption Private Input Credential Expiration Time expire String (8 bytes) After Local Credential Cipher Text Decryption In this embodiment, the technique for generating one-time key pair parameters is implemented as follows: Generation algorithm: Based on the secp256r1 elliptic curve, a one-time private key usk_o and a public key upk_o are generated through the wallet's built-in crypto.subtle.generateKey() interface (compliant with the Web Crypto API standard); Specific example values: One-time private key usk_o = 0x5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b (256 bits, used only in this proof generation, not stored after generation, and destroyed immediately after the proof is presented); The one-time public key upk_o = g^{usk_o} has the following coordinates after calculation: (upk_o.x = 0x6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c, upk_o.y = 0x7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d) (64 bytes, which is presented to the verifier along with the proof). The key rotation association parameters in this embodiment are as follows: Current epoch identifier: epoch_i = "20250620" (format YYYYMMDD, set according to "24-hour configurable period", automatically updated at 0:00 every day); Current user private key: Derived through the iterative formula usk_i = KDF(usk_{i-1}, epoch_i), where usk_{i-1} = 0x2c4e6f8a0b1d3c5e7f9a0b1c3d5e7f9a0b1c3d5e7f9a0b1c3d5e7f9a0b1c3d5e7f9a0b1c3d5e (previous day's private key). The KDF function is HKDF-SHA256. Example code for the derivation process: / / Key derivation code in the wallet plugin (Web Crypto API implementation) async function deriveCurrentUsk(uskPrev, epoch) { / / 1. Convert the previous day's private key into a 32-byte array const uskPrevBytes = new Uint8Array(32); new DataView(uskPrevBytes.buffer).setBigUint64(0, BigInt(uskPrev), false); / / 2. Construct HKDF parameters (salt value is an array of epoch bytes, and information field is "KeyRotation") const epochBytes = new TextEncoder().encode(epoch); const salt = epochBytes; const info = new TextEncoder().encode("KeyRotation"); / / 3. Import the previous day's private key as HKDF key material const keyMaterial = await crypto.subtle.importKey( "raw", uskPrevBytes, { name: "HKDF"}, false, ["deriveKey"] ); / / 4. Derive the current epoch private key (256 bits, corresponding to the secp256r1 private key length) const uskCurrent = await crypto.subtle.deriveKey( { name: "HKDF", hash: "SHA-256", salt, info}, keyMaterial, { name: "ECDSA", namedCurve: "P-256"}, / / P-256 is secp256r1 true, ["sign"] ); / / 5. Extract the raw bytes of the private key (only for proof generation, not for persistent storage) const uskCurrentRaw = await crypto.subtle.exportKey("raw",uskCurrent); return Array.from(new Uint8Array(uskCurrentRaw));} Current user public key: pk_i = g^{usk_i}, coordinates are (pk_i.x = 0x4e6f8a0b1d3c5e7f9a0b1c3d5e7f9a0b1c3d5e7f9a0b1c3d5e7f9a0b1c3d5e7f9a0b1c3d5e7f, pk_i.y = 0x5f7a9d1c3e5b7f9a1c3d5e7f9b1d3c5e7a9f1b3d5c7e9a1b3f5d7c9e1a2b3c4e), BBS+ signature σ has been bound to the current epoch through the pk_i.x, pk_i.y fields in the m vector. In this embodiment, this step 102 is divided into six sub-steps: "wallet obtains on-chain state-- > generates one-time key pair-- > decrypts local credential ciphertext-- > builds Groth16 circuit-- > generates zero-knowledge proof σ-- > two-dimensional code presentation", as follows: Sub-step 1: Wallet obtains the latest on-chain state Before presenting the credential, the user needs to synchronize the accumulator Acc and epoch information on the blockchain (only one-time networking is required, and subsequent proof generation can be completed offline), as follows: The user opens the wallet plug-in and clicks "educational credential-- > generate proof", and the wallet automatically initiates an HTTPS request to the blockchain node RPC interface ( https: / / testnet.yejieblockchain.com:8545 ); The wallet calls the getCurrentAcc() and getCurrentEpoch() functions of the 《AccumulatorManager.sol》 contract through Web3.js, as follows: async function getChainState() { const contract = new web3.eth.Contract(abi, "0x9876543210fedcba9876543210fedcba98765432"); / / Get the current accumulator Acc const currentAcc = await contract.methods.getCurrentAcc().call(); / / Get the current epoch (the contract is automatically updated through block time every day at 0 o'clock) const currentEpoch = await contract.methods.getCurrentEpoch().call(); / / Compare the local cached epoch with the on-chain epoch; if they are inconsistent, update the local usk_i. const localEpoch = localStorage.getItem("currentEpoch"); if (currentEpoch !== localEpoch) { const uskPrev = localStorage.getItem("uskPrev"); const newUsk = await deriveCurrentUsk(uskPrev, currentEpoch); localStorage.setItem("uskCurrent", newUsk); localStorage.setItem("currentEpoch", currentEpoch); localStorage.setItem("uskPrev", localStorage.getItem("uskCurrent")); / / Backup the current private key to the private key of the day before the next rotation. return { currentAcc, currentEpoch};} const { currentAcc, currentEpoch} = await getChainState(); console.log("Latest Acc on the chain:", currentAcc); / / Output: 0x7b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c9d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e console.log("Current epoch on the chain:", currentEpoch); / / Output: 20250620 The wallet caches the acquired currentAcc locally (localStorage, encrypted storage) for circuit constraint verification during subsequent proof generation. Sub-step 2: Generate a one-time user public / private key pair The wallet generates a one-time key pair (usk_o, upk_o) offline via the Web Crypto API. Code example: async function generateOneTimeKeyPair() { / / Generates a secp256r1 elliptic curve key pair const keyPair = await crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256"}, false, / / Non-persistent storage ["sign", "verify"] ); / / Extracts the one-time private key usk_o (raw format) const usk_o_raw = await crypto.subtle.exportKey("raw",keyPair.privateKey); const usk_o = Array.from(new Uint8Array(usk_o_raw)); / / Extracts the one-time public key upk_o (spki format, converted to coordinate form) const upk_o_spki = await crypto.subtle.exportKey("spki",keyPair.publicKey); const upk_o = parseSPKItoCoordinates(upk_o_spki); / / Custom function to parse SPKI format into public key coordinates return { usk_o, `upk_o};} / / Call the generation function const { usk_o, upk_o} = await generateOneTimeKeyPair(); console.log("One-time private key usk_o:", Buffer.from(usk_o).toString("hex")); / / Output: 0x5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6bconsole.log("One-time public key upk_o.x:",upk_o.x); / / Output: 0x6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c` The generated usk_o is only temporarily stored in memory, proving that it is destroyed immediately after generation via the crypto.subtle.wipeKey() interface to avoid the risk of leakage. Sub-step 3: Decrypt the local credential ciphertext to obtain private input The user's wallet stores the CID (QmXf8zL5a7b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f) generated in step 101. The encrypted CID needs to be downloaded from IPFS first, then decrypted using the key K derived from the current epoch's private key usk_i to obtain the private inputs (σƒ, t, attr, etc.) required for proof generation. The process is as follows: Downloading InterPlanetary File System (IPFS) ciphertext: The wallet passes through the IPFSHTTP gateway ( https: / / ipfs.yejieblockchain.com / ipfs / Download the encrypted file. Code example: async function downloadCiphertext(cid) { const response = await fetch(`https: / / ipfs.yejieblockchain.com / ipfs / ${cid}`); If (!response.ok) throws new Error("IPFS encrypted download failed"); const ciphertext = await response.arrayBuffer(); return new Uint8Array(ciphertext); / / 1528 bytes, corresponding to the finalCiphertext generated in step 101} const cid = localStorage.getItem("credentialCID"); / / Retrieve CID from local storage const ciphertext = await downloadCiphertext(cid); Derive decryption key: based on the current epoch private key usk_i and a random number t (need to decrypt t in ciphertext first, here we use "layered decryption": first decrypt the ciphertext header with usk_i to get t, then derive the complete decryption key with t and usk_i), code example as follows: async function deriveDecryptionKey(usk_i, ciphertext) { / / 1. Extract the IV (first 12 bytes) and authentication tag (last 16 bytes) from the ciphertext const iv = ciphertext.subarray(0, 12); const authTag = ciphertext.subarray(ciphertext.length - 16); const encryptedData = ciphertext.subarray(12, ciphertext.length -16); / / 2. First derive the temporary key K_temp with usk_i, decrypt the ciphertext header to get t const usk_i_bytes = new Uint8Array(await crypto.subtle.exportKey("raw", usk_i)); const K_temp = await hkdfDerive(usk_i_bytes, new TextEncoder().encode("TempKey"), 32); / / hkdfDerive is a custom HKDF function / / 3. Decrypt the ciphertext header (the first 32 bytes are the encrypted data of t) const tempCipher = await crypto.subtle.encrypt( { name: "AES-GCM", iv, additionalData: new Uint8Array(0),tagLength: 128}, await crypto.subtle.importKey("raw", K_temp, "AES-GCM",false, ["decrypt"]), encryptedData.subarray(0, 32) ); const t = new Uint8Array(tempCipher); / / 32 bytes, gets a random number t / / 4. Derive the complete decryption key K using usk_i and t (same as K=KDF(usk,t) in step 101) const K = await hkdfDerive(usk_i_bytes, t, 32, "CredentialEncryptionKey"); return { K, t}; const { K, t} = await deriveDecryptionKey(usk_i, ciphertext); Complete decryption to obtain JSON data: Decrypt the entire ciphertext using key K to parse out data such as σ, attr, and expire. Code example is shown below: async function decryptCredential(K, ciphertext) { const iv =ciphertext.subarray(0, 12); const authTag = ciphertext.subarray(ciphertext.length - 16); const encryptedData =ciphertext.subarray(12, ciphertext.length - 16); / / Import AES key const aesKey = await crypto.subtle.importKey("raw", K, "AES-GCM",false, ["decrypt"]); / / Decrypt const decryptedData = awaitcrypto.subtle.decrypt( { name: "AES-GCM", iv, additionalData:new Uint8Array(0), tagLength: 128}, aesKey,encryptedData ); / / Parse JSON const credentialJson = newTextDecoder().decode(decryptedData); return JSON.parse(credentialJson);}const credentialData = await decryptCredential(K, ciphertext); / / Extract the private input: σ, t, attr, usk_i, T, expire const { sigma: σ, t, attr, upk, expire} = credentialData; const T = { x: credentialData.Tx, y: credentialData.Ty}; / / Extract the public lock T from the decrypted data Sub-step 4: Constructing the Groth16 circuit and input A zero-knowledge proof circuit was built based on the gnark framework, implementing "four circuit constraints". The circuit code (in Go, compiled to WASM and integrated into a wallet) is shown below: (Zero-knowledge proof circuit design). package main import ( "github.com / consensys / gnark / frontend" "github.com / consensys / gnark / std / algebra / ec / secp256r1" "github.com / consensys / gnark / std / hash / sha256") / / Academic credential verification circuit structure type CredentialCircuit struct { / / Public input CurrentAcc secp256r1.Point `gnark:",public"` / / Current accumulator Acc PolicyID frontend.Variable `gnark:",public"` / / Policy ID hash APK secp256r1.Point `gnark:",public"` / / Issuer public key apk Challenge frontend.Variable `gnark:",public"` / / Challenge value / / Private inputs σ_R secp256r1.Point / / R point of BBS+ signature σ_s frontend.Variable / / s value of BBS+ signature t frontend.Variable / / Random number t AttrHash frontend.Variable / / SHA-256 hash of attribute attr (H1(attr)) ExpireHash frontend.Variable / / Hash of expiration time expire (H1(expire)) UPK_x frontend.Variable / / User public key upk.x UPK_y frontend.Variable / / User public key upk.y USK frontend.Variable / / User long-term private key usk_i T secp256r1.Point / / Public lock T / / Circuit definition: implement four constraints func (c *CredentialCircuit) Define(api frontend.API) error { / / 1. Constraint 1: BBS+ signature verification (BBS+.Verify(σ, m, apk) = 1) / / Construct message vector m = [H1(attr), H1(t), H1(expire), upk.x, upk.y] m := []frontend.Variable{c.AttrHash, api.Hash(sha256.New(api), c.t), c.ExpireHash, c.UPK_x, c.UPK_y} / / BBS+ signature verification logic (simplified version, based on gnark std library implementation) bbsVerifier := NewBBSPlusVerifier(api, c.APK) api.AssertIsEqual(bbsVerifier.Verify(c.σ_R, c.σ_s, m), 1) / / 2. Constraint 2: Prove T = g^t (g is the secp256r1 generator) g := secp256r1.Generator() T_computed := g.ScalarMul(api, c.t) / / Calculate g^t api.AssertIsEqual(c.T.X, T_computed.X) / / Assert that the calculated T matches the private input T api.AssertIsEqual(c.T.Y, T_computed.Y) / / 3. Constraint 3: Prove T is a factor of Acc (Acc mod T == 0, i.e., Acc = T * k, k is an integer) / / Based on the multiplicative property of the elliptic curve group: if T is a factor of Acc, then Acc * T^(q-2) = k (a multiple of the identity element) T_inv := c.T.ScalarMul(api, api.Sub(secp256r1.Order(), 2)) / / T^(q-2), q is the order of secp256r1 Acc_div_T := c.CurrentAcc.Add(api, T_inv) / / Acc * T_inv api.AssertIsEqual(Acc_div_T.X, 0) / / Assert that the result is the identity element (X=0, Y=0) api.AssertIsEqual(Acc_div_T.Y, 0) / / 4. Constraint 4: Satisfy business policies (e.g., "Graduation year >= 2020") / / Extract the hash of the graduation year field from AttrHash (assuming bytes 16-20 of AttrHash are the graduation year hash) gradYearHash := api.Slice(c.AttrHash, 16, 20) / / Preset hash value for "graduation year ≥ 2020" (pre-calculate the SHA-256 hash fragment of 2020) targetGradYearHash := frontend.Variable("0x1a2b3c4d") / / Example value api.AssertIsLessOrEqual(targetGradYearHash, gradYearHash) / / Asserts that the graduation year is ≥2020 return nil} After the circuit is compiled into WASM, the wallet loads the circuit via WebAssembly.instantiate(), ready to receive input data. Sub-step 5: Generate zero-knowledge proof σ The wallet passes "public input + private input" to the Groth16 circuit, calls the proof generation interface of the gnark framework to generate σ, and the process is as follows: The assembly input data is as follows: / / Assemble public input const publicInputs = { CurrentAcc: { X: currentAcc.x, Y: currentAcc.y}, / / Acc obtained on the chain PolicyID: new TextEncoder().encode("Academic Credential Verification_2025"), / / Policy ID provided by the verification party APK: { X: "0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", Y: "0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5"}, / / Issuer's public key obtained on-chain Challenge: new Uint8Array(32) / / Randomly generated challenge value}; / / Assemble private input const privateInputs = { σ_R: { X: σ.R_x, Y: σ.R_y}, / / decrypted BBS+ signature R point σ_s: σ.s, / / decrypted BBS+ signature s value t: t, / / decrypted random number t AttrHash: new TextEncoder().encode(attr), / / attribute attr hash ExpireHash: new TextEncoder().encode(expire), / / expiration time expire hash UPK_x: upk.x, / / decrypted user public key x coordinate UPK_y: upk.y, / / decrypted user public key y coordinate USK: usk_i, / / current epoch private key T: { X: T.x, Y: T.y} / / decrypted public lock T Next, call Groth16 to generate the proof: / / Load gnark WASM circuit const response = await fetch("credential_circuit.wasm"); const wasmBytes = await response.arrayBuffer(); const circuit = await WebAssembly.instantiate(wasmBytes); / / Import public parameters (pk.groth16 downloaded from IPFS) const pkResponse = await fetch("https: / / ipfs.yejieblockchain.com / ipfs / QmZ1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1 / pk.groth16"); const pk = new Uint8Array(await pkResponse.arrayBuffer()); / / Generate proof π const proof = circuit.instance.exports.generateProof( new Uint8Array(JSON.stringify(publicInputs)), new Uint8Array(JSON.stringify(privateInputs)), pk ); / / Parse proof π to byte array (288 bytes, Groth16 standard format) const π = new Uint8Array(proof); console.log("Zero-knowledge proof π (hexadecimal):", Buffer.from(π).toString("hex")); The generated σ contains mathematical evidence of "prover knowledge", which can be verified by the verifier through the on-chain contract without obtaining the input data. Sub-step 6: Present the proof in the form of a QR code The wallet concatenates "proof σ + one-time public key upk_o + CID" into JSON format and generates a QR code (compliant with ISO / IEC 18004 standard, version 10, error correction level H) through QRCode.js, as follows: The following presentation data is concatenated: { "proof": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "upk_o": { "x": "6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c", "y": "7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c3d5e4f5a6b7c8d" }, "cid": "QmXf8zL5a7b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f", "timestamp": "2025-06-20 14:30:00" / / Generation time, used to prevent replay. Next, the generated QR code is provided. An example of the code implementation is shown below: / / Use QRCode.js to generate a QR code function generateQRCode(data) { const qr = new QRCode(document.getElementById("qrcode-container"), { text: JSON.stringify(data), width: 200, height: 200, colorDark: "#000000", colorLight: "#ffffff", correctLevel: QRCode.CorrectLevel.H / / Highest error correction level, to prevent scanning errors}); return qr;} const produces Data = { proof: Buffer.from(π).toString("hex"), upk_o, cid,timestamp: new Date().toISOString()}; generateQRCode(showData); The user presents the QR code to the verifier, who can then scan it to obtain the verification data and initiate a verification request. In this embodiment, preferably, the one-time user public-private key pair generated on the user side is used to call the first elliptic curve accumulator to generate the first proof of the first credential data, and the first proof includes: the wallet client of the user generates a one-time user public-private key pair usk_o, upk_o; constructs the public input of zk-SNARK (Groth16): the current first elliptic curve accumulator data Acc, the policy ID; the private input: the Boneh-Gohrara-Sarkar enhanced BBS+ anonymous signature σ, a 256-bit random number t, user attribute data attr, and user private key data usk; circuit constraint 1: BBS+.Verify(σ, m, apk)=1; circuit constraint 2: T=g^t; circuit constraint 3: Acc mod T == 0 (T is a factor of Acc); circuit constraint 4: the range / policy meets the conditions, where m=[H1(attr), H1(t), H1(expire), upk.x, upk.y], H1 is a hash-to-elliptic curve function, expire is the expiration time of the credential, and apk is the public key of the issuer, where T is a public lock, and g is the generator of the secp256r1 elliptic curve; a corresponding proof π is generated using Groth16, and the proof π + upk_o + the content identifier of the first credential data is displayed in a two-dimensional code. Step 103: verifying the first proof of the first credential data presented In this embodiment, the verifier is an "XX Internet Technology Company" (an employer), which scans the two-dimensional code presented by the user through the "recruitment education verification system", calls the verify() function of the blockchain contract after obtaining the proof data, completes the legality verification of the zero-knowledge proof σ, and does not store user privacy data throughout the process, but only judges whether the credential is "valid / invalid". The core goal of this step is to quickly verify the legality of the credential (not tampered with, not revoked, and meet business policies) without obtaining user privacy, which specifically needs to meet: the verification process only relies on the on-chain contract and the presented data (σ, upk_o, CID), without the need to download IPFS ciphertext or access user attribute plaintext; the verify() function of the contract is called using "static call" to avoid consuming Gas (the verifier does not need to hold blockchain native tokens); the verification result is returned in real time (≤500ms), and batch verification is supported (such as verifying the credentials of multiple job seekers at the same time). The software environment (verification system) of the verifier in this embodiment is preferably a "recruitment education verification system" (Web end, version V3.0) developed based on Java Spring Boot, integrated with Web3j 5.0.0 (for blockchain interaction) and ZXing 3.5.1 (for two-dimensional code scanning). For the blockchain in this embodiment, the main chain is accessed through the blockchain API gateway of the project https: / / api.testnet.yejieblockchain.com The API call QPS is greater than or equal to 100, and concurrent verification is supported. The parameters of the contract verification function in this embodiment are preferably defined as follows. The input / output parameters and verification logic of the verification function "verify (π, PID)" are as follows: Parameter type Parameter name Data type / length effect source enter Zero-knowledge proofs Byte array (288 bytes) Proof data to be verified After the QR code is parsed enter Policy ID (PID) bytes32 (32 bytes) Business verification rule identifier (e.g., "Bachelor's degree or above") Verification system preset Output Verification results uint8 (1 byte) 200 = Valid, 403 = Invalid, 500 = Abnormal Contract execution returns Output Verify log hash bytes32(32 bytes) SHA-256 hash of the record for on-chain notarization Contract event emit The business policy rules in this embodiment are as follows: The verifier presets an "education verification policy" (PID=0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef), and the range / policy meets the conditions, with the specific rules being: 1. Graduation year ≥ 2020 (verified from the hash of attr, and the contract automatically judges by circuit constraint 4); 2. The education level is "bachelor" or "master" (for the same reason, verified by circuit constraint 4); The issuer is a university recorded by the education department (the contract verifies the legality of the issuer public key apk by the issuerApks list) The specific steps of step 103 in this embodiment are described as follows. This step 103 is divided into four sub-steps: "scanning and analyzing two-dimensional code--data legality pre-verification--calling on-chain contract verification--processing verification results", which are described in detail as follows: Sub-step 1: Scan and analyze the two-dimensional code to obtain the proof data The HR of the verifier scans the two-dimensional code presented by the user through the scanning function of the "recruitment education verification system" to obtain the proof data, and the process is as follows: The HR logs in to the system and enters the "education verification--code scanning verification" page, and the system calls the camera (which needs to be authorized by the user) to recognize the two-dimensional code through the ZXing library; Parse the JSON data in the two-dimensional code (corresponding to the "presentation data" generated in step 102), with the code example being: import com.google.zxing.BinaryBitmap; import com.google.zxing.MultiFormatReader; import com.google.zxing.Result; import com.google.zxing.client.j2se.BufferedImageLuminanceSource; import com.google.zxing.common.HybridBinarizer; import com.alibaba.fastjson.JSON; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.InputStream; public class QRCodeParser { / / Parse the QR code and return the proof data object public static ProofData parseQRCode(InputStream qrStream) throwsException { / / 1. Read the QR code image BufferedImage image = ImageIO.read(qrStream); / / 2. Convert to BinaryBitmap BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer( new BufferedImageLuminanceSource(image) )); / / 3. Parse the QR code content Result result = new MultiFormatReader().decode(bitmap); String qrContent = result.getText(); / / 4. Parse JSON into ProofData objects return JSON.parseObject(qrContent, ProofData.class);} / / Prove the data entity class (corresponding to the presentation of Data in step 102) public static class ProofData { private String proof; / / Zero-knowledge proof of π (hexadecimal string) private PublicKey upk_o; / / One-time public key private String cid; / / CID (credential) private String timestamp; / / Generate timestamp / / getter / setter omitted } / / Public key entity class public static class PublicKey { private String x; / / x-coordinate (hexadecimal) private String y; / / y-coordinate (hexadecimal) / / getter / setter omitted}} The system extracts the proof (converted to a byte array), upk_o, cid, and timestamp for subsequent pre-verification. Sub-step 2: Pre-validation of data validity Before calling the on-chain contract, the verification system performs local pre-verification to filter invalid data and reduce the number of on-chain calls. The pre-verification rules are as follows: Timestamp validity check: Check if the difference between the timestamp and the current time is ≤30 minutes (to prevent replay attacks). Code example: public static boolean checkTimestampValidity(String timestamp) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); sdf.setTimeZone(TimeZone.getTimeZone("UTC")); try { Date proofDate = sdf.parse(timestamp); long timeDiff = System.currentTimeMillis() -proofDate.getTime(); return timeDiff <= 30 * 60 * 1000; / / ≤30 minutes } catch (ParseException e) { return false;}} Proof format validation: Check if the length of the hexadecimal string of the proof is 576 characters (288 bytes × 2), and if it only contains the characters 0-9 and af. Code example: public static boolean checkProofFormat(String proofHex) { String regex = "^[0-9a-fA-F]{576}$"; return proofHex.matches(regex);} CID format verification: Checks if the cid conforms to the IPFS CID v0 format (starting with "Qm" and 46 characters in length). Code example: public static boolean checkCIDFormat(String cid) { String regex = "^Qm[0-9a-zA-Z]{44}$"; return cid.matches(regex);} Pre-verification result processing: If any verification fails, the system will directly return "Verification failed (400)" and prompt "The proof data is invalid, please present it again"; if all verifications pass, the system will proceed to the on-chain verification stage. Sub-step 3: Call the on-chain contract's verify() function to verify the proof. The verification system calls the verify() function of the blockchain's AccumulatorManager.sol contract via Web3j, using a "static call" (no modification to the on-chain state, no gas consumption), as follows: Initialize the Web3j client: import org.web3j.protocol.Web3j; import org.web3j.protocol.http.HttpService; import org.web3j.tx.gas.StaticGasProvider; import java.math.BigInteger; public class BlockchainClient { / / Blockchain testnet RPC address private static final String RPC_URL = "https: / / testnet.yejieblockchain.com:8545"; / / Static calls do not require gas; set the default gas price and cap. private static final BigInteger GAS_PRICE = BigInteger.ZERO; private static final BigInteger GAS_LIMIT = BigInteger.valueOf(3000000); / / Get the AccumulatorManager contract instance public static AccumulatorManager getAccumulatorManager() { Web3j web3j = Web3j.build(new HttpService(RPC_URL)); / / Contract address (the address deployed in step 100) String contractAddress = "0x9876543210fedcba9876543210fedcba98765432"; / / Static Gas Provider (No Actual Gas Consumption) StaticGasProvider gasProvider = new StaticGasProvider(GAS_PRICE, GAS_LIMIT); / / Return to contract instance return AccumulatorManager.load(contractAddress, web3j,gasProvider, gasProvider);}} Assemble contract call parameters: Convert the hex string of proof to a byte array: byte[] π = Numeric.hexStringToByteArray(proofData.getProof()); Convert the policy ID (PID) to bytes32: byte[] pid = Numeric.hexStringToByteArray("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"); Call the verify() function: public class ProofVerifier { public static VerificationResult verifyProof(ProofData proofData)throws Exception { / / 1. Get the contract instance AccumulatorManager contract = BlockchainClient.getAccumulatorManager(); / / 2. Assemble parameters byte[] π = Numeric.hexStringToByteArray(proofData.getProof()); byte[] pid = Numeric.hexStringToByteArray("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"); / / 3. Static call the verify() function (no transaction sent, only execute contract read-only methods) AccumulatorManager.VerifyResult result = contract.verify(π,pid).send(); / / 4. Parse the result VerificationResult verificationResult = newVerificationResult(); verificationResult.setCode(result.code.intValue()); / / 200=valid, 403=invalid verificationResult.setLogHash(Numeric.toHexString(result.logHash)); / / verification log hash verificationResult.setCid(proofData.getCid()); verificationResult.setTimestamp(proofData.getTimestamp()); return verificationResult;} / / VerificationResult entity class public static class VerificationResult { private int code; / / result code private String logHash; / / log hash private String cid; / / proof CID private String timestamp; / / proof generation time / / getters / setters omitted}} Contract verify() function internal logic: After the contract receives σ and PID, it performs the following operations: Load Groth16 verification key vk (download from IPFS, CID: QmZ1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1 / vk.groth16); Call gnark precompiled contract to verify the mathematical validity of σ€ (check if all circuit constraints are met); Check if the business policy corresponding to PID matches the constraint 4 in σ (e.g. "graduation year >= 2020"); If all steps pass, return code=200 and logHash (SHA-256 hash of the verification log); if any step fails, return code=403. Sub-step 4: Process the verification results and provide feedback to the user The verification system processes the verification result based on the code value returned by the contract and provides real-time feedback to the user. The process is as follows: Result judgment logic: public static String handleVerificationResult(VerificationResultresult) { switch (result.getCode()) { case 200: / / Verification successful: Log the message and return a success message. logVerificationResult(result, "Verification passed"); The academic credentials are valid; you may proceed to the next stage of the recruitment process. Case 403: / / Verification failed: Log the error and return the reason for the failure (without revealing the specific reason to protect privacy). logVerificationResult(result, "Verification failed (credentials invalid or revoked)"); The message "Your academic credentials are invalid or have been revoked. Please contact your graduating institution for confirmation." appears. default: / / System error: Prompt to retry logVerificationResult(result, "System error"); The system verification failed. Please try again in 5 minutes. User feedback methods: The system displays the results in real time on the web page (green "verification passed" or red "verification failed"); Send an SMS notification to the user's mobile phone (the user needs to provide their mobile phone number in advance). Example of the content: "[XX Internet Company] Your academic credentials verification result: valid, verification time: 2025-06-20 15:00:00, log hash: 0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcd." Step 104: Call the first elliptic curve accumulator based on the verified first credential data, remove the public lock in the updated first elliptic curve accumulator In this embodiment, the credential revocation is initiated by the issuer (XX University) or the regulator (local education department) for "invalid credentials after verification" (such as fake education background and information tampering). By obtaining the random number t of the credential, calculating the modular inverse T_{inv} of the public lock T, calling the remove() function of the AccumulatorManager.sol contract on the chain, and updating the elliptic curve accumulator Acc and marking the revocation of the public lock T. In this embodiment, the core goal of this step 104 is to solve the "O (n) time / space complexity" problem caused by the traditional certificate revocation relying on the Certificate Revocation List (CRL). Through the algebraic properties of the elliptic curve accumulator, the revocation operation with O(1) complexity is realized, while ensuring that the revoked credentials cannot pass the subsequent verification and resisting the risk of "credential resurrection" caused by blockchain fork rollback. Specifically, it needs to meet the following requirements: the revocation operation only needs to obtain the random number t, without knowing the user's identity or the credential content; after updating the accumulator Acc, the zero-knowledge proof σ of the revoked credential is invalid because "T is no longer a factor of Acc"; by mapping the revoked T through the contract revoked, T is prevented from being re-added after blockchain fork rollback. In this embodiment, technical parameters need to be configured when revoking credentials, as follows: (1) Revocation initiator and permission configuration Issuer: XX University Information Center (has the permission to call the remove() function, account address: 0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b, has been registered in the contract issuerList); Regulator: Local education department (has regulatory authority and can obtain t by decrypting tag, account address: 0x2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c, has been registered in the contract regulatorList); Permission control: The remove() function of the contract realizes permission verification through the onlyIssuerOrRegulator modifier, only the above two types of accounts can call, code example: modifier onlyIssuerOrRegulator() { require(issuerList[msg.sender] || regulatorList[msg.sender], "Only issuer or regulator can call"); ;} function remove(bytes calldata T, bytes calldata Tinv) external onlyIssuerOrRegulator returns (bytes memory newAcc) { / / Revocation logic (to be detailed later) (2) Elliptic curve modular inverse calculation parameters The elliptic curve is set to secp256r1 elliptic curve, and its order q and generator g are configured, preferably the specific parameters are as follows: Elliptic curve order q = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 (256-bit prime number, core parameter for modular inverse calculation); Public lock T (to be revoked): T = (T_x=0x7b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c, T_y=0x9d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e) generated in step 101; Modular inverse T_{inv} = T^(q-2): calculated by point multiplication operation of secp256r1 elliptic curve, which is essentially the "inverse element" of T in the elliptic curve group, satisfying the formula Acc′=Acc · T^(q-2). (3) Revocation transaction parameters Gas configuration: Revocation transaction needs to call the contract remove() function to perform "modular inverse verification + Acc update + revoked marking", and the estimated gas consumption is 200,000 (billion block chain test network gas price is about 100 Gwei, actual cost is about 0.02 EGC); Transaction confirmation requirement: Because it involves on-chain state update (Acc and revoked mapping), it needs to wait for 1 block confirmation; Event emit parameters: After successful revocation, the contract triggers the TRemoved event, containing parameters T (revoked public key), newAcc (updated accumulator), operator (caller address), timestamp (block timestamp), for subsequent audit and state synchronization. Step 104 of the embodiment is preferably divided into four sub-steps: "Obtain the random number t of the revoked credential", "Calculate the modular inverse T_{inv} of the public key", "Call the remove() function of the on-chain contract", and "Verify the revocation result". Details are as follows. Sub-step 1: Obtain the random number t of the revoked credential According to the different initiators, the ways to obtain t are divided into two categories: "Issuer directly obtains" and "Regulator decrypts tag to obtain". Both the issuer and the regulator can initiate the revocation: (1) Issuer (XX University) directly obtains t The issuer queries the application number of the revoked credential (such as AP202506200001, corresponding to the credential of user Zhang San in step 101) through the "credential management system". The system retrieves the random number t of the credential from the issuer database (MySQL 8.0, encrypted storage table credential_records). The query SQL example is as follows: SELECT t FROM credential_records WHERE application_no = 'AP202506200001' AND is_revoked = 0; -- Only query the revoked credential The database returns the hexadecimal value of t: 0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b (32 bytes, consistent with the t generated in step 101); The system confirms the revocation operation through "double administrator authorization" (information center director and dean of academic affairs department sign through UKey respectively) to prevent misoperation. The authorization process code example (Java implementation) is as follows: public class AuthorizationService { / / Double administrator UKey public key list private List <string>adminPublicKeys = Arrays.asList( "0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", "0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5"); / / Verify if the double signature is valid public boolean verifyDualSignature(byte[] data, List<byte[]> signatures) { if (signatures.size() != 2) return false; / / Verify that each signature matches the administrator's public key for (int i = 0; i < 2; i++) { String pubKey = adminPublicKeys.get(i); boolean valid = ECDSA.verify(data, signatures.get(i),pubKey, "secp256r1"); if (!valid) return false;} return true;}} / / Call the authorization service AuthorizationService authService = new AuthorizationService(); byte[] revokeData = ("AP202506200001_" + System.currentTimeMillis()).getBytes(); / / Data to be signed (including application number and timestamp, to prevent replay) List<byte[]> adminSignatures = getAdminSignatures(); / / Get dual administrator signatures boolean isAuthorized = authService.verifyDualSignature(revokeData,adminSignatures); if (!isAuthorized) throw new Exception("Dual administrator authorization failed, unable to initiate revocation"); (2) The regulator (education department) obtains t by decrypting the tag. If the issuing party does not respond or there is any irregularity in the issuance, the regulator can obtain t, and the process is as follows: The regulator queries the Issued event of the credential to be revoked (triggered when the issuer calls the add() function in step 101) through a blockchain explorer and obtains the tag in the event (28 bytes, example: 0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567). The regulator uses the locally stored regulatory private key rsk (256-bit, example: 0x8b3e5f7a9d1c3e5b7f9a1c3d5e7f9b1d3c5e7a9f1b3d5c7e9a1b3f5d7c9e1a2b) to derive the AES decryption key, as shown in the code example below: public class RegulatoryDecryptor { / / Derive the AES key from the supervisory private key rsk (the reverse process of deriving the encryption key from the issuer in step 101). public byte[] deriveAesKeyFromRsk(byte[] rskBytes) { BigInteger rsk = new BigInteger(1, rskBytes); / / Calculate the oversight public key rpk = g^rsk (verify consistency with on-chain rpk) ECPoint rpk = secp256r1Generator.multiply(rsk).normalize(); / / Derive the AES key from rpk.x (consistent with the deriveAesKey function in step 101) byte[] rpkXBytes = rpk.getAffineXCoord().toBigInteger().toByteArray(); byte[] salt = Arrays.copyOfRange(rpkXBytes, rpkXBytes.length- 32, rpkXBytes.length); HKDF hkdf = new HKDF(new SHA256Digest()); hkdf.init(salt, "RegulatoryTagKey".getBytes(StandardCharsets.UTF_8)); byte[] aesKey = new byte
[32] ; hkdf.generateBytes(aesKey, 0, 32); return aesKey;} / / Decrypt tag to get t public byte[] decryptTag(byte[] aesKey, byte[] tag) { / / Tag format: IV (12 bytes) + ciphertext (68 bytes) + authentication tag (16 bytes) byte[] iv = Arrays.copyOfRange(tag, 0, 12); byte[] ciphertext = Arrays.copyOfRange(tag, 12, tag.length - 16); byte[] authTag = Arrays.copyOfRange(tag, tag.length - 16,tag.length); / / AES-GCM decryption Cipher cipher = Cipher.getInstance("AES / GCM / NoPadding"); SecretKeySpec secretKeySpec = new SecretKeySpec(aesKey, "AES"); GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, gcmSpec); byte[] plaintext = cipher.doFinal(ByteBuffer.allocate(ciphertext.length + authTag.length) .put(ciphertext).put(authTag).array()); / / Extract t (first 32 bytes) from plaintext (84 bytes) return Arrays.copyOfRange(plaintext, 0, 32);}} / / Call decryption service RegulatoryDecryptor decryptor = new RegulatoryDecryptor(); byte[] rskBytes = Hex.decode("8b3e5f7a9d1c3e5b7f9a1c3d5e7f9b1d3c5e7a9f1b3d5c7e9a1b3f5d7c9e1a2b"); byte[] aesKey = decryptor.deriveAesKeyFromRsk(rskBytes); byte[] tagBytes = Hex.decode("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567"); byte[] t = decryptor.decryptTag(aesKey, tagBytes); / / Get t, which is consistent with the t in the issuer's database Sub-step 2: Calculate the modular inverse of the public lock T_{inv} Whether the issuer or the regulator, after obtaining t, the public lock T = g^t needs to be calculated first (skip if already stored), and then the modular inverse T_{inv} = T^(q-2) of T is calculated. The specific process is as follows: (1) Calculate the public lock T (if not directly obtained) If only t is obtained, T = g^t is calculated through the point multiplication operation of the secp256r1 elliptic curve. The code example (consistent with the logic of the issuer calculating T in step 101) is as follows: public class PublicLockCalculator { private static final ECPoint g = secp256r1Generator; / / secp256r1 generator (synchronous step 100) private static final BigInteger q = secp256r1Order; / / secp256r1 order (synchronous step 100) public ECPoint calculateT(byte[] tBytes) { BigInteger t = new BigInteger(1, tBytes).mod(q); / / Ensure t is in the range [1, q-1] return g.multiply(t).normalize(); / / Point multiplication calculates T = g^t / / Call the calculation function PublicLockCalculator calculator = new PublicLockCalculator(); ECPoint T = calculator.calculateT(t); String THex = Hex.toHexString(T.getEncoded(false)).substring(2); / / Convert to 64-byte hexadecimal (remove the compression identification bit) / / Output the hexadecimal value of T: 0x7b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c9d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e (2) Calculate the inverse T_{inv} Based on Fermat's Little Theorem of elliptic curves (secp256r1), since q is a prime number, the modular inverse of T is equal to T^(q-2). The dot product operation is implemented using the Bouncy Castle library. Code example: public class ModInverseCalculator { private static final BigInteger q = secp256r1Order; private static final BigInteger exponent = q.subtract(BigInteger.valueOf(2)); / / q-2 public ECPoint calculateTinv(ECPoint T) { / / Calculate Tinv = T^(q-2) (dot product) return T.multiply(exponent).normalize();}} / / Call the modular inverse calculation function ModInverseCalculator invCalculator = new ModInverseCalculator(); ECPoint Tinv = invCalculator.calculateTinv(T); String TinvHex = Hex.toHexString(Tinv.getEncoded(false)).substring(2); / / 64-byte hexadecimal Sub-step 5.3.3: Call the on-chain contract's remove() function to update the accumulator. The initiator calls the remove() function of the AccumulatorManager.sol contract via Web3j, passing in T and T_{inv}, to complete the accumulator update and the revocation flag of T. The process is as follows: (1) Initialize the contract by calling the client Continue using the Web3j client configuration from step 103, ensuring it matches the on-chain contract address. A code example is shown below: public class RevokeClient { private static final String RPC_URL = "https: / / testnet.yejieblockchain.com:8545"; private static final String CONTRACT_ADDRESS = "0x9876543210fedcba9876543210fedcba98765432"; private static final BigInteger GAS_PRICE = BigInteger.valueOf(100_000_000_000L); / / 100 Gwei private static final BigInteger GAS_LIMIT = BigInteger.valueOf(200_000); public AccumulatorManager getContractInstance(String privateKey){ Web3j web3j = Web3j.build(new HttpService(RPC_URL)); / / Load the initiator's account (transactions require private key signature) Credentials credentials = Credentials.create(privateKey); / / Return to contract instance return AccumulatorManager.load( CONTRACT_ADDRESS, web3j, credentials GAS_PRICE, GAS_LIMIT );}} (2) Send the remove() transaction The initiator signs the transaction using their private key and calls the remove() function. Code example: public class RevokeService { public TransactionReceipt revokeCredential(String privateKey,String THex, String TinvHex) throws Exception { / / 1. Obtain a contract instance RevokeClient client = new RevokeClient(); AccumulatorManager contract = client.getContractInstance(privateKey); / / 2. Convert T and Tinv to byte arrays byte[] T = Numeric.hexStringToByteArray(THex); byte[] Tinv = Numeric.hexStringToByteArray(TinvHex); / / 3. Send the remove() transaction TransactionReceipt receipt = contract.remove(T, Tinv).send(); / / 4. Verify whether the transaction was successful if (!receipt.isStatusOK()) throw new Exception("Transaction reversal failed, transaction hash: " + receipt.getTransactionHash()); return receipt;}} / / Call the revocation service (taking the issuer as an example, the private key is the issuer's account private key). RevokeService revokeService = new RevokeService(); String issuerPrivateKey = "0x3e5f7a9d1c3e5b7f9a1c3d5e7f9b1d3c5e7a9f1b3d5c7e9a1b3f5d7c9e1a2b3c"; String THex = "7b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c 9d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e"; String TinvHex = "8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b"; TransactionReceipt receipt = revokeService.revokeCredential(issuerPrivateKey, THex, TinvHex); System.out.println("Revocation transaction hash: " + receipt.getTransactionHash()); / / Example: 0xcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcd System.out.println("Updated Acc: " + receipt.getEvents().get("TRemoved").get("newAcc")); (3) Internal logic of the contract remove() function After the contract receives T and T_{inv}, it performs the following core operations: Verification not revoked: Check if revoked[hash(T)] is false; if it is true, reject the operation. Verify validity: Call the secp256r1 pre-compiled contract to calculate T·T_{inv}, and reject the operation if the result is not a unit. Update accumulator: Execute Acc = Acc·Tinv (dot multiplication), overwriting the current currentAcc; Mark as revoked: Set `revoked[keccak256(T)] = true` to prevent `T` from being re-added; Trigger the TRemoved event: emit the event with T, newAcc, msg.sender, block.timestamp as parameters, code example (Solidity) as follows: event TRemoved(bytes indexed T, bytes newAcc, address indexedoperator, uint256 timestamp); function remove(bytes calldata T, bytes calldata Tinv) externalonlyIssuerOrRegulator returns (bytes memory newAcc) { bytes32 THash = keccak256(T); / / 1. Verify that T is not revoked require(!revoked[THash], "T has been revoked"); / / 2. Verify the validity of Tinv (T·Tinv = identity element) bytes memory product = secp256r1.multiply(T, Tinv); require(keccak256(product) == keccak256(bytes64(0)), "InvalidTinv"); / / 3. Update Acc newAcc = secp256r1.multiply(currentAcc, Tinv); currentAcc = newAcc; / / 4. Mark T as revoked revoked[THash] = true; / / 5. Trigger the event emit TRemoved(T, newAcc, msg.sender, block.timestamp); return newAcc;} Sub-step 4: Verify the revocation result After cancelling a transaction confirmation, it is necessary to verify that "Acc has been updated" and "T has been marked as canceled" to ensure that subsequent verifications will fail. The process is as follows: To query the updated Acc: Call the contract's getCurrentAcc() function and confirm that the return value is newAcc (in units in the example), which is consistent with newAcc in the TRemoved event; To check the revocation status of T: call the contract's revoked(keccak256(T)) function and confirm that the return value is true; Simulated subsequent verification: Using the proof σ generated in step 102, the contract verify() function is called, confirming that code=403 (invalid) is returned because "T is no longer a factor of newAcc". Circuit constraint 3 verification fails. Code example: ProofVerifier verifier = new ProofVerifier(); ProofData oldProofData = getOldProofData(); / / Proof data generated in step 102 VerificationResult result = verifier.verifyProof(oldProofData); System.out.println("Verification result code after cancellation: " + result.getCode()); / / Output: 403 (Invalid) Step 105: Synchronize the updated first elliptic curve accumulator data in the blockchain nodes. In this embodiment, step 105 proceeds in the order of "block confirmation --> full node synchronization --> light node synchronization", as detailed below: Sub-step 1: Cancel the transaction and confirm it on the blockchain (trigger synchronization) The remove() transaction sent in step 104 needs to be packaged into a block and confirmed through the consensus mechanism before it can trigger network-wide synchronization. The process is as follows: Transaction broadcasting: After the initiator (such as the issuer) sends the remove() transaction, the transaction is broadcast to the blockchain P2P network (nodes communicate via TCP / IP protocol, port 4001), and all validating nodes can receive it; Block Proposal: The block proposer (e.g., Node001, staked ≥ 1 million EGC) selected by PoS consensus will package the transaction and other pending transactions into a new block (Block10000), calculate the block hash (SHA-256) and sign it. PBFT Confirmation: The block proposer sends the new block to PBFT validators (21 in total, the top 21 nodes by staked amount), which need to be verified by ≥14 nodes (2 / 3 of the total) to confirm the "transaction legitimacy" (e.g., remove() caller authority, Tinv validity) and sign it. Block Chain: The confirmed Block10000 is added to the main chain, and the block contains: Transaction List: The remove() transaction (including T, Tinv, and caller address) in step 104; State Change: The currentAcc of the contract AccumulatorManager is updated from Acc_{old} (the Acc before step 104) to Acc_{new}, and the revoked[hash(T)] is updated from false to true. Event Log: TRemoved event (including T, Acc_{new}, caller address, and block timestamp). At this point, the block confirmation is complete, and the synchronization process is officially started. Sub-step 2: Full Node Synchronization Update Acc_{new} Full nodes (e.g., issuer nodes, regulator nodes) store the complete ledger and need to synchronize and execute the state changes in the block in real time to ensure that the local Acc is consistent with the main chain. The process is as follows: Block Download: Full nodes download the complete data of Block10000 (including block header, transaction list, and state change proof) from the block proposer or other synchronized nodes through the P2P network, with a download speed of ≥1MB / s (to meet the size requirement of ≤2MB per block); Second Verification of Transaction Legitimacy: Full nodes execute the remove() transaction in Block10000 and recalculate: Verify if the caller is an account in issuerList or regulatorList (authority verification); Verify if Tinv is the valid modular inverse of T (T·Tinv = identity element); Recalculate Acc_{new} = Acc_{old}·Tinv and confirm that it is consistent with the Acc_{new} recorded in the block. Local state update: If the verification is passed, the full node updates the local contract state: sets currentAcc to Acc_{new} and revoked[hash(T)] to true. If the verification fails (e.g., the block data is tampered with), the full node refuses to synchronize the block and automatically downloads from other nodes (anti-tampering mechanism). Synchronization broadcast: the full node that has completed synchronization broadcasts Block10000 and the updated Acc_{new} to the P2P network to accelerate synchronization of other nodes ("relay synchronization" strategy). Taking the issuer full node (IP: 10.0.0.10, hardware configuration: Intel Xeon Gold 6330 CPU, 256GB RAM, 2TB SSD) as an example, the time-consuming of synchronizing Block10000 is about 500ms, of which the transaction verification takes ≤100ms (Acc point multiplication calculation depends on hardware acceleration), and the state update takes ≤50ms, fully meeting the real-time requirements. Sub-step 3: Light node synchronization Light nodes (such as verifier nodes and user wallet nodes) do not store the complete ledger and need to obtain and verify Acc_{new} through "full node state proof" to avoid the storage overhead of full synchronization. The process is as follows: initiating a state query request: before calling the verify() function, the light node (such as the PC wallet used by the verifier HR) initiates an Acc query request to the specified full node (such as the blockchain API node) through HTTPS: https: / / api.testnet.yejieblockchain.com ) with the parameters "contract address + state variable name (currentAcc)". Local validity check: after obtaining Acc_{new} and the Merkle proof returned by the full node, the light node confirms that Acc_{new} has not been tampered with (to avoid false Acc provided by the full node) through the process of "calculating the storage hash---> recursively verifying the Merkle path---> comparing the block header state root". Caching: after the verification is passed, the light node caches Acc_{new} locally (such as the localStorage of the user wallet or the Redis cache of the verifier system), with a cache validity period of 30 minutes (requery the full node after expiration) to reduce repeated synchronization overhead. In this embodiment, preferably, the regulator can be configured to use its private key rsk to decrypt the tag, obtain T=g^t after calculating t, retrieve the user information corresponding to the first credential data through the Issued event triggered on the chain at the time of issuance, and realize regulatory tracking. Wherein in this embodiment, t is the tag, t is a random number, and g is the generator of the secp256r1 elliptic curve. Please refer to the aforementioned parameters in this example. Example 2: To more clearly reveal the working details of the credential data processing system involved in the present application, please refer to Figure 2 , Figure 2 The business architecture diagram of the credential data processing system provided in Embodiment Two of the present application. The present embodiment takes "financial credit qualification anonymous credential" as the application scenario, and based on the credential data processing system 200 shown in FIG. 2, a set of distributed architecture integrating "Ant Chain main chain L1 201" "issuer Issuer 202" "IPFS / database encrypted large file 203" "user wallet client 204" "verifier node contract / gateway 205" "regulator Regulator 206" is constructed to realize "privacy verification, efficient revocation, and compliance regulation" of credit qualification. In view of the pain points of the financial field "credit qualification review needs to balance user privacy and institutional risk control": users need to prove that their credit is good (such as no overdue records, income up to standard), but are reluctant to expose sensitive information such as ID card number and bank flow; financial institutions need to quickly verify the qualification and prevent fraud; regulatory agencies need to audit the compliance of credit issuance. The present credential data processing system 200 realizes the coordinated satisfaction of the needs of the three parties through the "on-chain lightweight storage + off-chain privacy calculation" architecture. In the present embodiment, the Ant Chain main chain L1 201 only stores three types of core data (in line with the "main chain lightweight" design): Elliptic curve accumulator Acc=∏Ti (records the public lock product of all valid credit credentials, used for validity verification); Strategy / attribute commitment hash (such as the hash value of "income ≥ 500,000 / year", used for policy constraints of zero-knowledge proof); Regulatory public key rpk (used by the regulator to decrypt the regulatory tag to realize compliance tracking). IPFS / database encrypted large file 203: stores encrypted credential ciphertext (including BBS+signature, random number t, regulatory tag, etc.), zero-knowledge proof (zk) circuit, and historical accumulator snapshot (used for node synchronization and disaster recovery). Wherein in the present embodiment, the issuer 202 can be a certain commercial bank (such as "XX Bank"), holding a BBS + signature key pair (ask, apk), wherein apk = g^{ask} (g is the secp256r1 generator), responsible for auditing user credit qualifications and issuing credentials. User wallet 204: an individual user applying for credit, holding a long-term private key usk (used to derive encryption keys and signatures) and a one-time private key usk_o (used for zero-knowledge proof privacy protection), and the public keys are upk = g^{usk} and upk_o = g^{usk_o} respectively. Validator node 205: a certain credit institution (such as "XX Consumer Finance Company"), calling the verification function through the on-chain contract gateway to determine whether the credential is valid. Regulator 206: the State Administration of Financial Supervision and Management, holding a key pair (rsk, rpk) (rpk = g^{rsk}), used to decrypt the regulatory label and audit credit issuance compliance. Wherein the elliptic curve in the present embodiment is preferably secp256r1, order q = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141, generator g = (0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296, 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5). Hash function: H1:{0,1} ∗ -- 》Z q : Map any data to an integer within the secp256r1 order (used for BBS + signature message mapping); H2:G -- 》{0,1} 256 : Hash the elliptic curve point to 256-bit bytes (used for accumulator element hash storage); H3: Merkle Hash function (used for attribute commitment, hiding user sensitive information). Elliptic curve accumulator: initial value Acc init = g (multiplication equivalence of elliptic curve group unit element), add credentials when Acc' = Acc ⋅ T (T = g^t is a public lock), delete when Acc' = Acc ⋅ T^(q-2) (T^(q-2) is the modular inverse of T). The following describes the step flow in the present embodiment. Step 1: Credit qualification credential issuance (Issuer -- > User) The user applies for credit to XX Bank, and the bank issues an anonymous credential containing attributes such as "no overdue credit inquiry, income ≥ 500,000 / year, credit limit ≤ 1,000,000" after passing the audit, and the process is as follows: 1.1 User submits qualifications and public key The user submits a credit application through the "XX Bank APP" (integrates wallet function) and fills in the attribute data attr = {name: Li Si, ID number: XXX, income: 600,000 / year, credit record: no overdue, application limit: 800,000}; The APP locally generates a long-term key pair (usk, upk), where usk = 0x4e6f8a0b1d3c5e7f9a0b1c3d5e7f9a0b1c3d5e7f9a0b1c3d5e7f9a0b1c3d5e7f9a0b1c3d5e7f, upk = (upk_x = 0x5f7a9d1c3e5b7f9a1c3d5e7f9b1d3c5e7a9f1b3d5c7e9a1b3f5d7c9e1a2b3c4e, upk_y = 0x6a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b) ; The APP submits attr (processed by preliminary hash) and upk to the XX Bank background (issuer 202) through HTTPS (TLS 1.3). 1.2 Issuer generates credential core components The XX Bank background generates a random number t, a public lock T, a BBS + signature σ, and a regulatory tag tag based on the audit results: Generate 256-bit random number: SecureRandom secureRandom = new SecureRandom(); byte[] t = new byte
[32] ; secureRandom.nextBytes(t); Calculate public lock T = g^t: ECPoint g = secp256r1Generator; / / secp256r1 generator BigInteger tBig = new BigInteger(1, t); ECPoint T = g.multiply(tBig).normalize(); Generate BBS + signature sigma: Construct message vector m = [H1(attr), H1(t), H1(expire=20261231), upk.x, upk.y], where H1(attr) is the elliptic curve mapping point of the attribute. The issuer signs with the private key ask: BigInteger ask = new BigInteger("1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b", 16); ECPoint[] mPoints = {H1(attrBytes), H1(t), H1(expireBytes),upkXPoint, upkYPoint}; Tuple<ECPoint, BigInteger> sigma = BBSPlusSigner.sign(ask, mPoints); Generate regulatory label tag: Construct encrypted data \text{plaintext} = t || \text{expire} || H3(\text{attr}) || \text{issuerID=XX Bank_001}, encrypt with regulatory public key rpk to derive AES key: byte[] aesKey = deriveAesKeyFromRpk(rskBytes); / / Derive AES key from regulatory private key rsk byte[] tag = AESGCMEncrypt(aesKey, plaintext); 1.3 On-chain publishing and off-chain storage On-chain publishing (Ant Chain main chain L1 201): The issuer calls the add(T, comm, tag) function of the Ant Chain contract AccumulatorManager, where comm = H3(t || attr) (attribute commitment), and after the transaction is triggered: The elliptic curve accumulator is updated to Acc' = Acc ⋅ T; The comm, tag, and regulatory public key rpk are stored on-chain. The following are stored off-chain (IPFS 203): The issuer organizes "σ, t, tag, comm, expire, upk, rpk" into JSON, encrypts it with K = KDF(usk, t), uploads it to IPFS, generates a CID (such as QmBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890), and returns the CID to the user's wallet. Step 2: Creditworthiness certificate generation and presentation (User--》Verifier) When the user applies for a loan from XX Consumer Financial Company, they need to present a creditworthiness certificate, and the process is as follows: 2.1 Generate one-time key pair and zero-knowledge proof π The user's wallet 204 completes the following operations offline: Generate a one-time key pair: const { usk_o, upk_o} = await generateOneTimeKeyPair(); Decrypt IPFS ciphertext to obtain private input: The wallet downloads the ciphertext from IPFS through the CID, decrypts it with K = KDF(usk, t), and obtains σ, t, attr, T, and other data. Build a Groth16 zero-knowledge proof: The circuit constraints include: Verify that σ is legal (bind the issuer's public key apk); Prove that T = g^t; Prove that T is a factor of the current Acc (Acc mod T == 0); Meet business policies (such as "income ≥ 500,000 / year" and "no overdue records"). The wallet generates the proof π using the gnark framework and generates a two-dimensional code with "π, upk_o, CID" to present to the verifier. Step 3: Creditworthiness certificate verification (Verifier--》Chain) XX Consumer finance company calls AntChain contract to verify proof through validator node 205, and the process is as follows: 3.1 Parse proof and on-chain verification The verifier scans the two-dimensional code to obtain π, upk o, and CID; Call the verify(π, PID) function of the AccumulatorManager contract of AntChain (PID = "credit qualification_income≥500,000"); The contract verifies the legitimacy of π and returns the result (200 = valid, 403 = invalid). Step 4: Credit qualification certificate revocation (Issuer / Regulator -- Chain) If the user's credit qualification is invalid (e.g., overdue), the issuer or regulator initiates revocation: 4.1 Obtain random number t and calculate modular inverse The issuer obtains t from the database, or the regulator decrypts tag to obtain t; Calculate T_inv = T^(q-2) (the modular inverse of T). 4.2 Call on-chain contract to update accumulator The issuer calls the remove(T, T_inv) function, and AntChain main chain 201 executes: The accumulator is updated to Acc' = Acc ⋅T_inv; Mark revoked[ hash(T)] = true. Step 5: Regulatory tracking (Regulator -- Chain + IPFS) When the State Administration of Financial Supervision and Regulation needs to audit credit issuance compliance: Decrypt tag to obtain t, and calculate T = g^t; Through the Issued event on AntChain, retrieve the user attribute commitment comm corresponding to T and the issuer ID; Combine the hash of the IPFS ciphertext to confirm whether the credit issuance is compliant. In this embodiment 2, the elliptic curve accumulator can be efficiently revoked: by the "multiplicative group inverse" property, the time complexity of the revocation operation is reduced from O (n) to O (1), and only one point multiplication operation is required to remove the certificate from the accumulator (Acc' = Acc ⋅T^{q-2}), solving the efficiency bottleneck of the traditional CRL scheme. Privacy protection of zero-knowledge proof: the user presents π, which only proves that the "certificate is legal and meets the policy", without exposing any attribute plaintext (such as income, credit record), realizing "data available and invisible". On-chain and off-chain layered storage: the main chain only stores core data (accumulator, commitment, public key) within 256 bytes, greatly reducing the on-chain storage pressure; IPFS stores encrypted ciphertext, uses distributed storage to ensure data reliability, and ensures privacy through user key encryption. Reduced storage cost: compared with on-chain storage of full-amount certificates, the on-chain storage cost of this scheme is reduced by more than 90% (single certificate on-chain occupies ≤256 bytes); improved verification efficiency: zero-knowledge proof verification takes ≤100ms (on-chain static call), which is 80% higher than traditional PKI verification (requires certificate chain verification, takes ≥500ms); privacy leakage risk reduced to 0: the verifier cannot obtain the user attribute plaintext, and can only judge the validity of the certificate through the proof result; regulatory compliance guarantee: the regulator can trace the source and attribute commitment of each credit through the tag and on-chain events, meeting the financial audit requirements. Example 3: To more clearly reveal the more working details of the key rotation processing in the certificate data processing system related to the present application, see Figure 3 , Figure 3 The key rotation processing flowchart of the certificate data processing system provided in Embodiment Three of the present application. Referring to FIG. 3 (time axis and key evolution diagram), this embodiment three takes "bank credit card qualification certificate" as the application scenario, focusing on the technical solution of key rotation and forward security, which fully reveals the technical principle of "long-term identity key iteration by epoch, signature binding to current public key, and old private key leakage does not affect historical certificates". The scene positioning is: the bank (issuer) issues "no overdue credit card qualification certificate" to the user, and the user presents the certificate when applying for credit card installment, while the bank needs to avoid the risk of "long-term key leakage leading to all historical certificates being forged" through key rotation, and the regulator (State Administration of Financial Supervision and Regulation) can audit compliance through the regulatory tag. Figure 3 takes "time axis" as the core, and the left side is vertically labeled with "user long-term identity key", "current day epoch key", and "historical credentials still valid". The horizontal direction is divided into three time periods: epoch i-1, epoch i, and epoch i+1. Each period corresponds to the binding relationship of "long-term private key usk- public key pk- credential signature σ". The specific mapping is as follows: Figure 3 visual elements Actual scene meaning (credit card qualification certificate) Timeline Time period sequence of key rotation, this embodiment is set to "24 hours / period" Epoch i-1 The first rotation period (example: 2025-10-01 00:00:00~2025-10-01 23:59:59) Epoch i The second rotation period (example: 2025-10-02 00:00:00~2025-10-02 23:59:59) Epoch i+1 The third rotation period (example: 2025-10-03 00:00:00~2025-10-03 23:59:59) usk_{i-1} / pk_{i-1} User long-term key pair of epoch i-1 period, pk_{i-1}=g^{usk_{i-1}} (g is the secp256r1 generator) usk_i / pk_i User long-term key pair of epoch i period, derived from usk_{i-1} by KDF (usk_i=KDF(usk_{i-1}, epoch_i)) usk_{i+1} / pk_{i+1} User long-term key pair of epoch i+1 period, derived from usk_i Certificate A σ_A Credit card qualification certificate signature issued in epoch i-1 period, bound to pk_{i-1} Certificate A σ_B Credit card qualification certificate signature updated in epoch i period, bound to pk_i Certificate A σ_C Credit card qualification certificate signature extended in epoch i+1 period, bound to pk_{i+1} "Historical certificate still valid" flag Even if usk_i of epoch i is leaked, σ_A (epoch i-1) and σ_B (epoch i) are still legal and cannot be forged The elliptic curve in this embodiment is secp256r1, the order q = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141, the generator g = (0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296, 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5); Key derivation function (KDF): HKDF-SHA256 (compliant with RFC 5869), used for usk iteration, the information field is fixed as "CreditCardCredential_KeyRotation"; Rotation period: 24 hours (trigger method: rotateRpk() multi-sign transaction called by bank multi-administrator at 0 o'clock every day; BBS + signature parameters: message vector m = [H1(attr), H1(t), H1(expire), pk.x, pk.y], H1 is a "SHA-256 + elliptic curve mapping" function (mapping data to a point on the secp256r1 curve); On-chain storage: Yijie blockchain main chain (L1) stores elliptic curve accumulator Acc (records the product of valid credentials T) and supervision public key rpk (national financial supervision and administration public key), single credential on-chain occupancy ≤256 bytes; Off-chain storage: IPFS (version 0.18.1) stores credential ciphertext (contains σ, t, tag), CID example: QmCrEfGhIjKlMnOpQrStUvWxYz0123456789ab. Among them, the core roles in this embodiment are defined as follows: Issuer: A commercial bank (e.g., "XX Bank Credit Card Center"), holding the BBS + signature private key ask = 0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b, public key apk = g^{ask}, responsible for key rotation trigger and certificate issuance; User: Credit card holder (e.g., "Wang Wu"), managing usk for each epoch through the mobile banking APP application (integrating wallet functions), storing keys locally and not exposing them externally; Verifier: Bank credit card installment audit department, verifying the validity of the certificate through the on-chain contract verify(π) function; Regulator: State Administration of Financial Supervision and Regulation, holding the regulatory key pair (rsk, rpk), rsk = 0x8b3e5f7a9d1c3e5b7f9a1c3d5e7f9b1d3c5e7a9f1b3d5c7e9a1b3f5d7c9e1a2b, rpk = g^{rsk}, used for auditing key rotation compliance. In this embodiment, the process timeline is as follows: from epoch i-1 to epoch i+1, the complete link of "key generation--->certificate issuance--->key rotation--->forward security verification" is presented, and each step contains reproducible technical details and parameter examples. Step 1: epoch initialization and rotation configuration (on-chain deployment) Before epoch i-1 (2025-10-01), the bank completes the on-chain configuration of key rotation to implement the "rotation event": Deploy rotation management contract: In this embodiment, the 《KeyRotationManager.sol》 contract is deployed on the blockchain, with core functions including "record current epoch" "store pk hash of each epoch" "trigger multi-signature rotation", contract address: 0x9876543210fedcba9876543210fedcba98765432; Configure rotation rules: Set "rotation period = 24 hours" "multi-signature threshold = 2 / 3" (2 out of 3 bank administrators need to sign to confirm rotation) in the contract, code example: contract KeyRotationManager { uint256 public currentEpoch; / / Format: YYYYMMDDHH (e.g., 2025100100) mapping(uint256 = bytes32) public epochPkHash; / / epoch--》pkHash mapping(address = bool) public adminList; / / Administrator list uint256 public requiredSignatures; / / Multi-signature threshold / / Initialization: Set initial epoch, administrators, and multi-signature threshold function initialize(uint256 _initialEpoch, address[] memory _admins, uint256 _requiredSignatures) external { currentEpoch = _initialEpoch; requiredSignatures = _requiredSignatures; for (uint256 i = 0; i < _admins.length; i++) { adminList[_admins[i]] = true;}} / / Multi-signature triggered key rotation: Update currentEpoch function rotateEpoch(uint256 _newEpoch, bytes[] memory _adminSignatures) external { / / Verify the number of signatures meets the threshold require(_adminSignatures.length >= requiredSignatures, "Insufficient signatures"); / / Verify each signature's administrator authority for (uint256 i = 0; i < _adminSignatures.length; i++) { address admin = recoverSigner(abi.encodePacked(_newEpoch), _adminSignatures[i]); require(adminList[admin], "Invalid admin signature");} / / Update the current epoch currentEpoch = _newEpoch; emit EpochRotated(_newEpoch);} / / Auxiliary function: recover the admin address from the signature function recoverSigner(bytes memory _data, bytes memory _signature) internal returns (address) { bytes32 hash = keccak256(_data); (bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature); return ecrecover(hash, v, r, s);}} In this embodiment, the initial epoch is set: call the initialize() function, set_initialEpoch=2025100100 (2025-10-01 00:00),_admins=[bank administrator A address, administrator B address, administrator C address], and_requiredSignatures=2 to complete the initialization. Step 2: epoch i-1 (2025-10-01): key generation and credential A issuance (σ_A binds pk_{i-1}) Substep 2.1 User generates the long-term key pair of epoch i-1 (usk_{i-1} / pk_{i-1}) The user generates the key pair of epoch i-1 through the mobile banking APP (wallet client), corresponding to usk_{i-1} and pk_{i-1} in FIG. 3: Initial key generation: When the APP is started for the first time, the root key rootKey = 0x2c4e6f8a0b1d3c5e7f9a0b1c3d5e7f9a0b1c3d5e7f9a0b1c3d5e7f9a0b1c3d5e is generated by "OS random number + hardware fingerprint" (only stored in the user's mobile phone security chip, not uploaded to the cloud); Derivation usk_{i-1}: Based on the root key and the epoch i-1 identifier, usk_{i-1} is derived by HKDF-SHA256, code example (Java implementation): public class KeyDerivation { / / HKDF-SHA256 derivation usk public static byte[] deriveUsk(byte[] rootKey, String epoch) { HKDF hkdf = new HKDF(new SHA256Digest()); / / Salt value: UTF-8 bytes of epoch byte[] salt = epoch.getBytes(StandardCharsets.UTF_8); / / Information field: fixed identifier byte[] info = "CreditCardCredential_KeyRotation".getBytes(StandardCharsets.UTF_8); hkdf.init(rootKey, salt, info); byte[] usk = new byte
[32] ; / / 256bit, conform to secp256r1 private key length hkdf.generateBytes(usk, 0, 32); return usk;} public static void main(String[] args) { byte[] rootKey = Hex.decode("2c4e6f8a0b1d3c5e7f9a0b1c3d5e7f9a0b1c3d5e7f9a0b1c3d5e7f9a0b1c3d5e"); String epoch = "2025100100"; byte[] usk_i_1 = deriveUsk(rootKey, epoch); System.out.println("usk of epoch i-1: " + Hex.toHexString(usk_i_1));}} Next, calculate pk_{i-1}: Based on the secp256r1 curve, calculate pk_{i-1}=g^{usk_{i-1}}, code example: public class PublicKeyCalculator { private static final ECPoint g = secp256r1Generator; / / secp256r1 generator public static ECPoint calculatePk(byte[] usk) { BigInteger uskBig = new BigInteger(1, usk); return g.multiply(uskBig).normalize(); / / Calculate the public key using dot multiplication. public static void main(String[] args) { byte[] usk_i_1 = Hex.decode("3d5e7f9a0b1c3d5e7f9a0b1c3d5e7f9a0b1c3d5e7f9a0b1c3d5e7f9a0b1c3d5f"); ECPoint pk_i_1 = calculatePk(usk_i_1); System.out.println("pk.x of epoch i-1: " + pk_i_1.getAffineXCoord().toBigInteger().toString(16)); System.out.println("pk.y of epoch i-1:" + pk_i_1.getAffineYCoord().toBigInteger().toString(16));}} Wherein the key storage is implemented as follows: usk_{i-1} is encrypted and stored in the mobile phone security chip (AES-256 encryption is used, and the key is derived from the user's gesture password), and pk_{i-1} is temporarily stored by the APP and used for subsequent certificate issuance. Sub-step 2.2 Bank issues certificate A (σ_A binds pk_{i-1}): The user applies for a credit card qualification certificate to the bank, and the bank issues σ_A binding pk_{i-1} after passing the audit, corresponding to "Certificate A σ_A" in FIG. 3: The user submits application data: The APP submits the user attributes attr={"name: Wang Wu, ID number: XXX, credit card number: XXXX, overdue record: none, credit score: 800"} and pk_{i-1} to the bank background through HTTPS (TLS 1.3); The bank generates the certificate core component: Generate a 256-bit random number t: t=0x2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b (revocation key); Calculate the public lock T: T=g^t, T.x=0x3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c, T.y=0x4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d; Generate the supervision tag tag: tag=AES-GCM(rpk, t||expire||attrHash||issuerID), expire=20261001 (certificate validity period 1 year), attrHash=H3(attr) (Merkle Hash), issuerID=XX Bank Credit Card Center_001, tag=0x8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b; Generate BBS + signature σ_A: Construct the message vector m=[H1(attr), H1(t), H1(expire), pk_{i-1}.x, pk_{i-1}.y], and the bank signs with ask: public class BBSPlusSigner { public static Tuple<ECPoint, BigInteger> sign(BigInteger ask,ECPoint[] m) { / / Generate a random number r for the signature BigInteger r = new BigInteger(secp256r1Order.bitLength(), newSecureRandom()).mod(secp256r1Order); / / Calculate R = g^r * ∏(m_i^r) (BBS + signature core logic) ECPoint R = g.multiply(r).normalize(); for (ECPoint mi : m) { R = R.add(mi.multiply(r)).normalize();} / / Calculate the challenge value c byte[] cBytes = Hash.sha256(ArrayUtils.addAll(R.getEncoded(), apk.getEncoded(), mBytes)); BigInteger c = new BigInteger(1, cBytes).mod(secp256r1Order); / / Calculate the signature s = r + c*ask mod q BigInteger s = r.add(c.multiply(ask)).mod(secp256r1Order); return new Tuple<>(R, s); / / σ_A=(R, s)} public static void main(String[] args) { BigInteger ask = new BigInteger("1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b", 16); ECPoint[] m = {H1(attrBytes), H1(tBytes), H1(expireBytes), pk_i_1_x_Point, pk_i_1_y_Point}; Tuple<ECPoint, BigInteger> σ_A = sign(ask, m); System.out.println("σ_A.R.x:" + σ_A.getKey().getAffineXCoord().toBigInteger().toString(16)); System.out.println("σ_A.s:" + σ_A.getValue().toString(16));}} Then on-chain and off-chain storage: On-chain: Bank calls the add(T, comm, tag) function of the _AccumulatorManager.sol contract, comm=H3(t||attr), updates Acc=Acc·T, and triggers the Issued(T, Acc) event. Off-chain: Organize "σ_A, t, tag, comm, pk_{i-1}" into JSON, encrypt it with K=KDF(usk_{i-1}, t), upload it to IPFS, generate CID=QmCrEfGhIjKlMnOpQrStUvWxYz0123456789ab, and return CID to the user APP. Step 3: epoch i (2025-10-02): Key rotation and credential A update (σ_B binds pk_i) Substep 3.1 Trigger epoch rotation (multi-signature update currentEpoch) 2025-10-02 00:00, bank administrator triggers key rotation: Administrator initiates multi-signature request: Administrator A submits_newEpoch=2025100200 through the bank backend and generates signature sig_A; Administrator B supplements signature: After Administrator B's review, generate signature sig_B (reach the threshold requiredSignatures=2); Calling the rotateEpoch() function: The bank's backend calls rotateEpoch(2025100200, [sig_A, sig_B]) in KeyRotationManager.sol via Web3j, the contract updates currentEpoch=2025100200, and triggers the EpochRotated(2025100200) event. Sub-step 3.2 The user generates the key pair (usk_i / pk_i) for epoch i. When the user's app connects to the internet, it automatically detects an update to currentEpoch, generates a key pair for epoch i, and corresponds to... Figure 3 usk_i and pk_i: Key Derivation: The app reads usk_{i-1} from the security chip and derives a new private key using usk_i=KDF(usk_{i-1}, "2025100200"). Code example: String epoch_i = "2025100200"; byte[] usk_i = KeyDerivation.deriveUsk(usk_i_1, epoch_i); System.out.println("usk of epoch i:" + Hex.toHexString(usk_i)); Next, calculate pk_i: pk_i = g^{usk_i}, pk_i.x = 0x7b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c, pk_i.y = 0x8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d; Old key backup: The APP backs up usk_{i-1} to a local encrypted folder (without deleting it) for use in verifying historical credentials σ_A. Sub-step 3.3 Update voucher A to σ_B (binding pk_i) Because the user's credit score has been increased to 820, the certificate attributes need to be updated. The bank will reissue σ_B bound to pk_i, corresponding to... Figure 3 "voucher A σ_B": User submits updated attributes: APP submits new attributes attr={"name: Wang Wu, ID number: XXX, credit card number: XXXX, overdue record: none, credit score: 820"} with pk_i; Bank generates σ_B: Similarly, generates new t'=0x3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c, T'=g^{t'}, tag', and constructs message vector m'=[H1(attr'), H1(t'), H1(expire), pk_i.x, pk_i.y], generates σ_B=(R', s'); Storage update: New credential ciphertext uploaded IPFS (CID=QmDsFgHiJkLlMnOpQrStUvWxYz0123456789cd), APP synchronously updates local storage's σ with pk. Step 4: epoch i+1 (2025-10-03): Continuously rotate and extend credential A (σ_C binds pk_{i+1}) Process consistent with epoch i: Rotation trigger: Administrator A, C signs trigger currentEpoch=2025100300; Key generation: usk_{i+1}=KDF(usk_i, "2025100300"), pk_{i+1}=g^{usk_{i+1}}; Credential extension: User applies for "credit card limit increased to 50,000", bank issues σ_C binding pk_{i+1}, corresponding to "Credential A σ_C" in Figure 3, IPFS CID=QmEfGhIjKlMnOpQrStUvWxYz0123456789ef. Step 5: Simulate old private key leakage and forward security verification Substep 5.1 Scenario simulation: usk_i leakage of epoch i Suppose the user's mobile phone is lost, usk_i=0x6a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b is obtained by the attacker, the attacker tries to forge σ_A of epoch i-1 and σ_B of epoch i, and verifies the forward security (corresponding to Figure 3 "Historical credentials still valid" mark). Substep 5.2 Attackers' attempt to forge σ_A and reasons for failure The attacker needs to generate σ_A' (fake σ_A), which is analyzed as follows: The core of BBS + signature depends on the generation of σ_A=(R, s), which depends on "pk_{i-1} at that time" and "signature random number r", where r is derived from usk_{i-1} and attribute data of epoch i-1 (r=KDF(usk_{i-1}, attr||t)); Limitations of the attacker: The attacker only holds usk_i and cannot reverse usk_{i-1} (KDF is a one-way function, and the original key cannot be obtained from the derived key); Even if the attacker obtains pk_{i-1} through the Issued event on the chain, it cannot generate r (r depends on usk_{i-1}), and thus cannot calculate R=g^r * ∏(m_i^r) and s=r + c*ask; Fake verification fails: the attacker generates σ_A' because "R and s do not match pk_{i-1}", and when the verifier calls verify(π), the circuit constraint ① (BBS+.Verify(σ, m, apk)=1) of zero-knowledge proof fails, returning 403 (invalid). Sub-step 5.3 Attackers' attempt to forge σ_B and the reasons for failure The attacker holds usk_i and tries to regenerate σ_B, but also fails: The uniqueness of the signature random number r: the r of σ_B is the random number at the time of epoch i issuance, which is only stored in the bank background and user APP, and the attacker cannot obtain it; Tamper-proof of on-chain events: the Issued event on the chain records T' and comm corresponding to σ_B, and the T''=g^{t''} and comm''=H3(t''||attr'') corresponding to the σ_B' generated by the attacker cannot match the on-chain record, and the circuit constraint ③ (Acc mod T == 0) fails when verifying. Sub-step 5.4 Conclusion: forward security is established Even if usk_i of epoch i is leaked, the attacker can only affect the credentials of epoch i+1 and later (usk_{i+1} needs to be replaced in time), and cannot forge the historical credentials of epoch i-1 and epoch i. The design goal of "historical credentials still valid" in Figure 3 is achieved. Step 6: Offline user key synchronization and risk mitigation During the offline period of the user's mobile phone (such as flight mode), epoch i has completed rotation, and the APP will automatically execute the following process when it is connected to the network next time: State comparison: APP invokes the on-chain contract to obtain currentEpoch = 2025100200 and the latest Acc, and compares the locally cached epoch = 2025100100 and the old Acc; Key update: finding that the epochs are inconsistent, automatically deriving usk_i and pk_i to replace the old local key; Proof regeneration: finding that the Acc is updated (e.g., other credentials are revoked), regenerating the zero-knowledge proof π of the current credential to ensure that the subsequent presentation is verified. In the present embodiment, preferably, when the client program of the user wallet is networked, the "local cached Acc / epoch" and the "latest Acc / epoch on the blockchain" are automatically compared, where the epoch is the current iteration period identifier, and the Acc is the current first elliptic curve accumulator data. If the epoch is updated, a new usk_i and pk_i are automatically generated. If the Acc is updated, the corresponding proof π is regenerated using Groth16. The embodiments of the module classes described in the above embodiments are merely illustrative, wherein the units described as separate components can or can not be physically separate. Part or all of the modules can be selected to achieve the purpose of the present embodiment scheme according to actual needs. Those of ordinary skill in the art can understand and implement without creative labor. Finally, it should be noted that: the above embodiments are only used to illustrate the technical solutions of the present application, and not to limit them; although the present application has been described in detail with reference to the foregoing embodiments, those of ordinary skill in the art should understand: they can still modify the technical solutions recorded in the foregoing embodiments, or make equivalent replacement for part of the technical features; and these modifications or replacements do not make the essence of the corresponding technical solutions deviate from the spirit and scope of the technical solutions of the embodiments of the present application.< / string>
Claims
1. A method for processing voucher data, comprising: Deploy the first elliptic curve accumulator on the blockchain; Based on the user attribute data and user public key data uploaded by the user, the issuer generates a random number, a public lock, a Bonne-Boyan-Sakam enhanced anonymous signature, and a regulatory tag to form the first credential data, and generates and saves the content identifier corresponding to the first credential data; Based on the one-time user public-private key pair generated on the user side, the first elliptic curve accumulator is invoked to generate the first proof of the first credential data, and the first proof is presented. Verify the first proof of the presented first voucher data; Based on the verified first credential data, the first elliptic curve accumulator is invoked, and the public lock in the first elliptic curve accumulator is removed and updated; The nodes in the blockchain synchronize the updated first elliptic curve accumulator data.
2. The method according to claim 1, characterized in that, The first elliptic curve accumulator is configured as follows: Based on the secp256r1 elliptic curve, the addition of the first voucher data is achieved by calculating the dot product using the formula Acc′=Acc · T (where T=g^t, t is a random number, g is the generator of the secp256r1 elliptic curve, and T is the public lock). Based on the secp256r1 elliptic curve, the first credential data is revoked by multiplying the data using the formula Acc′=Acc · T^(q-2) (where T^(q-2) is the modular inverse of T, q is the order of the secp256r1 elliptic curve, and T is the public lock).
3. The method according to claim 1, characterized in that, The process of generating the first credential data based on user attribute data and user public key data uploaded by the user, including the following: (The data comprises a random number, a public lock, a Bonne-Boyan-Sakam enhanced anonymous signature, and a regulatory tag.) The user submits user attribute data (attr) and user public key data (upk); The issuer generates a 256-bit random number t, a public lock T = g^t (g is the generator of the secp256r1 elliptic curve), a Bonne-Boyan-Sakham enhanced anonymous signature σ = BBS + Sign(ask, m) (ask is the issuer's private key, m = [H1(attr), H1(t), H1(expire), upk.x, upk.y], H1 is the hash to elliptic curve function, expire is the expiration time of the credential, upk.x and upk.y are the coordinate components of the user's public key), and a regulatory tag tag = AES-GCM(rpk, t||expire||attrHash||issuerID) (issuerID is the issuer ID, rpk is the regulator's public key). On the blockchain, the first credential data is issued by emitting Issued(T, Acc) using the formula add(T, comm=H3(t||attr), tag), where t is a 256-bit random number, attr is user attribute data, tag is regulatory tag, T is the public lock, Acc is the current first elliptic curve accumulator data, comm is attribute commitment, and H3 is Merkle hash calculation.
4. The method according to claim 1, characterized in that, Saving the content identifier corresponding to the generated first voucher data includes: Generate an encryption key K = KDF(usk, t), where usk is the user's private key, t is a 256-bit random number, and KDF is the key derivation function; Organize the Bonne-Boyan-Shaqam enhanced anonymous signature, 256-bit random number, regulatory label, attribute commitment, credential expiration time, user public key, and regulator's public key corresponding to the first credential data in JSON format, and generate the ciphertext of the first credential data using the formula ciphertext=AES-GCM(K, JSON); The ciphertext is stored in the InterPlanetary File System or a distributed database, and the hash value of the ciphertext is used as the content identifier of the first credential data and sent to the user's wallet client for storage.
5. The method according to claim 1, characterized in that, The first proof, which is generated based on a one-time user public-private key pair generated on the user's end, and which calls the first elliptic curve accumulator to generate the first credential data, and presents the first proof, includes: The user's wallet client generates a one-time user public-private key pair, usk_o and upk_o; The public inputs for constructing zk-SNARK (Groth16) are: the current first elliptic curve accumulator data Acc, and the policy ID; the private inputs are: Bonne-Boyan-Sakam enhanced BBS + anonymous signature σ, 256-bit random number t, user attribute data attr, and user private key data usk; circuit constraint 1: BBS+.Verify(σ, m, apk)=1; circuit constraint 2: T=g^t; circuit constraint 3: Acc mod T == 0 (T is a factor of Acc); circuit constraint 4: the range / policy satisfies the condition, where m=[H1(attr), H1(t), H1(expire), upk.x, upk.y], H1 is the hash to elliptic curve function, expire is the expiration time of the credential, apk is the issuing public key, where T is the public lock, and g is the generator of the secp256r1 elliptic curve. Use Groth16 to generate the corresponding proof π, and then display the content identifier corresponding to proof π + upk_o + first voucher data in a QR code format.
6. The method according to claim 1, characterized in that, The first proof for verifying the presented first credential data includes: The user submits the first proof π, the one-time user public key upk_o, and the content identifier corresponding to the first credential data to the verifier. The verifier calls the verification function verify(π, PID) through the on-chain contract, where PID is the policy ID identifier. After the verification function returns a confirmation, the first proof of the first voucher data is verified.
7. The method according to claim 1, characterized in that, The step of calling the first elliptic curve accumulator based on the verified first credential data and removing / updating the public lock in the first elliptic curve accumulator includes: The issuer or regulator obtains the random number t of the verified first credential data and calculates Tinv=T^(q-2), where T^(q-2) is the modular inverse of T, q is the order of the secp256r1 elliptic curve, T is the public lock T=g^t, and g is the generator of the secp256r1 elliptic curve. Call the on-chain contract related to the first elliptic curve accumulator, remove(T, Tinv), to update the first elliptic curve accumulator, Acc = Acc·Tinv, and mark revoked[hash(T)] as true.
8. The method according to claim 1, characterized in that, Further includes: The user key usk is iterated according to the first configurable period, and the iteration formula is usk_i=KDF(usk_{i-1}, epoch_i) (KDF is the key derivation function, and epoch_i is the current iteration period identifier), and the corresponding public key pk_i=g^usk_i, where g is the generator of the secp256r1 elliptic curve; Bind the Bonne-Boyan-Schaqam enhanced anonymous signature σ to the pk_i at that time.
9. The method according to claim 8, characterized in that, Further includes: When the user wallet client connects to the internet, it automatically compares the "local cached Acc / epoch" with the "latest Acc / epoch on the blockchain". The epoch is the identifier of the current iteration cycle, and the Acc is the data of the current first elliptic curve accumulator. If the epoch is updated, new usk_i and pk_i are automatically generated; if the Acc is updated, the corresponding proof π is generated again using Groth16.
10. The method according to claim 1, characterized in that, Further includes: The regulator uses its private key rsk to decrypt the tag, obtains t, and calculates T=g^t. It then retrieves the user information corresponding to the first credential data through the on-chain Issue event triggered during issuance, thus achieving regulatory tracking. Here, tag is the regulatory label, t is a random number, and g is the generator of the secp256r1 elliptic curve.
Citation Information
Patent Citations
Revocable security signcryption method for bank-enterprise direct connection scene
CN120165881A