Incident,
Source: De.Fi incident database.
- Audits before it
- 4.
- Audits since
- 1, the latest dated 19 May 2024.
As of 25 Oct 2026, OKX has 5 audit reports by 1 security firm on Trustblock; the latest is by CertiK, dated 19 May 2024. Those reports list no open critical findings. It also holds 1 incident (latest 12 Dec 2023).
Record last updated . It holds audits and findings, contracts and incidents up to Oct 2026. Trustblock does not review code.
Newest first
| Audit | Security firm | Date | Scope | Critical findings | Report |
|---|---|---|---|---|---|
| OKX SDK | Security firm: CertiK | Date: | Scope: Not recorded (see the report) | No open critical findings | Open · skynet.certik.com |
| OKX (Threshold-lib) | Security firm: CertiK | Date: | Scope: Repository at commit ece0117 | 1 critical finding, fixed | Open · skynet.certik.com |
| OKX - Audit 3 | Security firm: CertiK | Date: | Scope: Files named in the report | No open critical findings | Open · skynet.certik.com |
| OKC Swap | Security firm: CertiK | Date: | Scope: Files named in the report | No open critical findings | Open · skynet.certik.com |
| OKX Marketplace of Solana | Security firm: CertiK | Date: | Scope: Not recorded (see the report) | No open critical findings | Open · skynet.certik.com |
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 1 |
| High | 1 | 1 | 1 |
| Medium | 0 | 1 | 6 |
| Low | 43 | 0 | 25 |
The audited version of the library was found to be vulnerable to the Lindell17 Abort Vulnerability (CVE-2023-33242), where an attacker could potentially extract the full private key from a wallet implementing the Lindell17 2PC protocol. This vulnerability arises from deviations in the Lindell17 implementations from the specification of the academic paper, particularly in handling aborts during failed signature attempts. It was discovered that an attacker, with privileged access, could exploit this vulnerability to exfiltrate the key after approximately 200 malicious signature requests, thereby posing a severe security risk to the affected systems and their users. The root cause of the Lindell17 Abort Vulnerability stems from some implementations of the Lindell17 protocol mishandling or ignoring aborts in cases of failed signatures. This oversight allows an attacker, assuming privileged access, to exploit this flaw and extract a full private key by initiating malicious signature requests, posing a significant security threat to the affected systems. Vulnerability detail: [https://www.fireblocks.com/blog/lindell17-abort-vulnerability-technical-report](https://www.fireblocks.com/blog/lindell17-abort-vulnerability-technical-report)
Open the reportAll the instructions that update the protocol configurations forward their logic to the helper function `update_protocol_config_account`. This function modifies the data of the global protocol configuration (`ProtocolConfig` account). It can change the protocol's: - Status: if the status is `false`, the program is frozen, this means that all the marketplace instructions are not callable - Fee base points - Fee recipient - Manager The mentioned helper function checks if the caller is signing the transaction but it doesn't check if the caller is the `protocol_manager` saved on the global protocol configuration (`ProtocolConfig` account). This means that the instructions using this helper function can be called by anyone, not only by the `protocol_manager`.
Open the reportIn the mentioned code snippet, the developers use the go routine function `crypto.GenerateSafePrime` to generate two safe prime numbers. The `crypto.GenerateSafePrime` function takes `quit` channel parameter as a control signal to terminate the prime generation process. Using code from `paillier.go` as an example: ```go var values = make(chan *big.Int) var quit = make(chan int) var p, q *big.Int for p == q { for i := 0; i < currency; i++ { go crypto.GenerateSafePrime(PrimeBits/2, values, quit) } p, q = <-values, <-values close(quit) } ``` The above code creates a channel `quit` before launching go routines. The for loop checks if generated two prime numbers are the same. The loop continues if two same prime numbers are acquired. However, the channel `quit` is closed in every iteration of the for loop, meaning if the loop continues then the repeated `close` action on the already closed `quit` channel will cause unexpected `panic`.
Open the reportThe `for` constructs at the pointed locations initializes the `sharePubKeyMap` map with the public keys obtained from the secret shares commitments sent by the parties of the protocol. Such data are then used to verify the correctness of the owned share. The `map` initialization is stopped after $t+1$ iterations, where $t$ is the threshold to overcome to reconstruct the global shared secret. However, $n$ can be greater than $t+1$ as the number of parties participating in the protocol is not related to $t$. In this way, when $n > t+1$, the parties with ID higher than $t+1$ can not verify the correctness of their share and the DKG protocol is interrupted. The pointed locations refer to the correspondent steps the key generation and reshare, which both present the same incorrect behavior.
Open the reportThe key generation sub-protocol in [Lindell17](https://eprint.iacr.org/2017/552) ends up with the two parties having the global public key, their local private key share. Moreover, the entity playing party 2 also obtains a Paillier encryption of party 1 private share that will be necessary in the signing sub-protocol. In order to convince party 2 that the shared encryption actually is the chipertext corresponding to the local private share behind the shared partial public key, party 1 also sends to party 2 a proof demonstrating such assertion. A method to compute and verify such proof is reported in section 6 of [Lindell17](https://eprint.iacr.org/2017/552). In the scenario implemented in the codebase in scope, parties generate key shares using a combination of multiple (one per party) VSS sessions. Then, in order to run the Lindell protocols, the Paillier encryption of the local share is shared between the signing actors (the $c_{key}$ value). However, no proof that such encryption is correct is shared by party 1, so party 2 can only trust that the shared information is correct. In the case in which such encryption is not correct, party 1 can induce party 2 either in computing useless data or in participating in the calculation of a signature verifiable by a random key-pair that only party 1 is aware of.
Open the reportUsing code from `paillier.go` as example: ```go var values = make(chan *big.Int) var quit = make(chan int) var p, q *big.Int for p == q { for i := 0; i < currency; i++ { go crypto.GenerateSafePrime(PrimeBits/2, values, quit) } p, q = <-values, <-values close(quit) } ``` This code launches currency number of go routines in each iteration of the loop, each running the GenerateSafePrime function. However, only two values (i.e., `p` and `q`) are read from the values channel in each iteration. This means that if currency is greater than 2, there will always be some go routines whose results are not read in each iteration. These go routines will be blocked waiting for their results to be read, leading to a go routine leak. This leak can cause unnecessary resource consumption and potential performance degradation in the program.
Open the reportIn the following code, the developers did not check if curve is existed in curve map. In Go programming language, the value will be `nil` or `0` if key is not found in the map. ```go func GetCurveByName(curveName string) elliptic.Curve { return curveMap[curveName] } ``` This function is further used in the following code, which takes unsanitized JSON input and retrieve the curve by JSON `curve` field. ```go func (p *ECPoint) UnmarshalJSON(payload []byte) error { aux := &struct { Curve string X *big.Int Y *big.Int }{} if err := json.Unmarshal(payload, &aux); err != nil { return err } p.X = aux.X p.Y = aux.Y p.Curve = GetCurveByName(aux.Curve) if !p.IsOnCurve() { return fmt.Errorf("UnmarshalJSON error, point not on the curves ") } return nil } ``` However, the code will panic (caused by null pointer dereference) if curve's name is not precisely `secp256k1` or `ed25519`.
Open the reportThe current `DlnProof` implementation is largely borrowed from Binance open source threshold signature library. The corresponding code is located at [https://github.com/bnb-chain/tss-lib/blob/master/crypto/dlnproof/proof.go](https://github.com/bnb-chain/tss-lib/blob/master/crypto/dlnproof/proof.go). However, the adapted code reduced the number of components of the proof from the original `128` to `12`. This reduction in iterations weakens the security of the proof. In fact, the number of iterations in an interactive zk-proof (even though made non-interactive with the Fiat-Shamir heuristic) directly impacts the probability that the proof correctly demonstrates its assertion. A reduced number of iterations may make possible for an attacker to issue a proof which verifies as `true` while the underlying fact to demonstrate is `false`. While `12` iterations may speed up the computation, they do not represent a big enough value to trust the outcome of the proof verification, thereby potentially compromising the security of the system.
Open the reportThe okwallet-core component contains the AesEncrypt and AesDecrypt functions which use the encryption key that is hardcoded at `audit/okwallet-core/util/util.go:267`: ```GO var AesHardKey = common.MD5("1697773335922") // AesEncrypt AES 加密数据 func AesEncrypt(message, key string) (string, error) { if len(message) == 0 { return "", fmt.Errorf("message is empty") } if len(key) == 0 { key = AesHardKey } return common.Encrypt(message, key) } ``` No function calls towards these functions have been observed within the provided codebase. If this code snippet is not actively used, there are no security issues. **However, if this code is being used in other parts of the client’s software, this potentially poses a serious security problem.** ***Note:*** The reason this issue is undetermined is that we have only partial code. Although no reference to this hard coded secret was found in the code given to us, we can not conclude that code not shared with us did not use this hard-coded secret As such, the Severity is set as Informational, however it could be higher.
Open the reportWe identified a snippet of code that could allow an integer overflow if a sufficiently large integer is cast as a `uint32`. The snippet of code is found here: `bitcoin/brc20/util.go:15` ```Go func ConvertToUint32(v string) uint32 { i, _ := strconv.Atoi(v) return uint32(i) } ``` ***Note:*** This function is actively used in the codebase but it is not possible to determine if there is a real risk of an integer overflow in the client’s codebase, as we cannot establish the runtime values of the provided parameters. As such, the Severity is set as Informational, however it could be higher.
Open the reportIn the iOS app, we identified a snippet of code that potentially could free previously allocated memory. No concret memory manipulation risks (UAF) were identified in the client’s source code. The memory being free here (ptr) could be already freed in other calls, and ptr is not set to NULL (which is not a must in free). 
Open the reportThe `math/big` library is always included at the end on the import list, while according to the `gofmt` formatting, it should be placed with the Go standard library before external dependencies. Also, external dependencies should go at the end of the import list.
Open the reportRandom number generation plays a critical role in the threshold library. There are two main random number generation functions used in this library. 1. `getRandom` in `paillier.go` for generating a random number `r` which is `r < n` and `gcd(r,n) = 1`. ```go func getRandom(n *big.Int) (*big.Int, error) { gcd := new(big.Int) r := new(big.Int) var err error for gcd.Cmp(one) != 0 { r, err = rand.Int(rand.Reader, n) if err != nil { return nil, err } gcd = new(big.Int).GCD(nil, nil, r, n) } return r, nil } ``` 2. `RandomNum` in `utils.go` which generate a random number `r` which is `1 < r < n`. ```go func RandomNum(n *big.Int) *big.Int { if n == nil { panic(fmt.Errorf("RandomNum error, n is nil")) } for { r, err := rand.Int(rand.Reader, n) if err != nil { panic(fmt.Errorf("RandomNum error")) } if r.Cmp(one) == 1 { return r } } } ``` There are several inconsistent coding issues presented in the above code. 1. `getRandom` does not check against `n == 0` where `RandomNum` does. 2. `getRandom` returns error message where `RandomNum` simply panics if are any random number generation issue. 3. Both function do not check if `n` is negative value.
Open the reportThe address of cited paper regarding `NewPDLwSlackProof` design is invalid. Accessing to `https://www.cs.unc.edu/~reiter/papers/2004/IJIS.pdf` is no longer valid.
Open the reportInadequate Error Handling: The function `rand.Prime(rand.Reader, q.BitLen())` used in the `InitPolynomial` function is designed to return a randomly generated prime number and an error. The current implementation ignores this error, leading to a potential issue where the function could fail silently. If the random number generation fails for any reason, `r` may be `nil` or invalid, and this could lead to unexpected behavior or runtime errors when `r` is used later in the code. Lack of Boundary Checks: The `InitPolynomial` function accepts a parameter degree which is used to create an array and also controls the flow of a loop. Currently, there is no check to ensure that degree is a non-negative value. If a negative value is passed as degree, it could lead to unexpected behavior such as an error in creating the array or an infinite loop.
Open the reportThe BIP-32 specification makes a distinction between the generation of non-hardened and hardened key-pairs. The former are child key-pairs meant to work themselves as parent key-pairs for new derivations, while the latter do not allow for the derivation of a new public key from the parent public key. Such distinction is made explicit by partitioning the index space into two intervals, respectively $[0;2^{31}-1]$ and $[2^{31};2^{32}-1]$. However, the derivation algorithm implemented in the `NewChildKey` does not account for the described distinction and all indexes are used for the non-hardened derivation.
Open the reportThe current `DlnProof` implementation is largely borrowed from Binance open source threshold signature library. The corresponding code is located at [https://github.com/bnb-chain/tss-lib/blob/master/crypto/dlnproof/proof.go](https://github.com/bnb-chain/tss-lib/blob/master/crypto/dlnproof/proof.go). However, the adapted code removed the preliminary validation code from original function, as shown in the following: ```go if p == nil { return false } if N.Sign() != 1 { return false } modN := common.ModInt(N) h1_ := new(big.Int).Mod(h1, N) if h1_.Cmp(one) != 1 || h1_.Cmp(N) != -1 { return false } h2_ := new(big.Int).Mod(h2, N) if h2_.Cmp(one) != 1 || h2_.Cmp(N) != -1 { return false } if h1_.Cmp(h2_) == 0 { return false } for i := range p.T { a := new(big.Int).Mod(p.T[i], N) if a.Cmp(one) != 1 || a.Cmp(N) != -1 { return false } } for i := range p.Alpha { a := new(big.Int).Mod(p.Alpha[i], N) if a.Cmp(one) != 1 || a.Cmp(N) != -1 { return false } } ``` The `Verify` function of DLNProof Algorithm takes input from other party and it is necessary to perform preliminary checking to ensure the legitimacy of passed parameters before performing actual verification step.
Open the reportThe code to initiate a signature session for the ECDSA algorithm takes as an initialization parameter the `message` to sign. Such parameter will not be taken into consideration before the second message by party 2, since some coefficient generation is necessary before proceeding with the calculation of the final signature components. The `message` parameter is treated as an hexadecimal string and converted into its byte representation. However, no check is enforced on `message` in the initialization phase. If the passed string is not a valid hexadecimal string, the implementation would abort the signing procedure with an error and the effort put in the first part of the protocol may be wasted.
Open the reportThe [BIP-32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) specification provides a method to generate child key-pairs from a parent one through an hash function and exploiting the linearity of operations in the elliptic curve group. When, given a chaincode, an index, and a parent key-pair, a new key is calculated, there are two outcomes that need to be validated before the procedure can be successfully concluded. First, the integer resulting from the 32 least significant bytes of the HMAC-SHA512 outcome must be strictly less than the order of the elliptic curve (the `offset` variable). Second, the obtained public key (`ecPoint` variable) must not be the point-at-infinite of the curve. In either cases the procedure did not generate a valid child key pair. In particular, the BIP-32 document specifies that a new generation attempts should be made with the next value of the index. Even though there is vary low probability that the describes conditions arise, the algorithm should manage them in order to cover all the possible cases.
Open the reportThe reference paper reporting the parameters for the proof of correctness in the Pailler key-pair generation is outdated. In fact, it currently reports an update of the Lindell algorithm, made by Lindell et al., which gets rid of the Pailler homomorphic encryption scheme.
Open the reportIn the function `_exeForks()`, multiple swaps are performed, with each swap swapping `batchAmount * weight / 10000` tokens. ```solidity=86 function _exeForks(address payer, uint256 batchAmount, RouterPath calldata path) private { address fromToken = bytes32ToAddress(path.fromToken); // execute multiple Adapters for a transaction pair uint256 pathLength = path.mixAdapters.length; for (uint256 i = 0; i < pathLength; ) { bytes32 rawData = bytes32(path.rawData[i]); address poolAddress; bool reserves; uint256 weight; assembly { poolAddress := and(rawData, _ADDRESS_MASK) reserves := and(rawData, _REVERSE_MASK) weight := shr(160, and(rawData, _WEIGHT_MASK)) } require(weight >= 0 && weight <= 10000, "weight out of range"); uint256 _fromTokenAmount = (batchAmount * weight) / 10000; _transferInternal(payer, path.assetTo[i], fromToken, _fromTokenAmount); ``` Since each swap swaps a portion of `batchAmount`, it is expected for all of `batchAmount` to be used. This would require the sum of all weights to be 10000, but no such check exists, allowing the possibility that some tokens are unused or tokens meant for a different batch are used.
Open the reportThe function `_smartSwapInternal()` simultaneously iterates over the arrays `batchesAmount` and `batches` to be used when calling `_exeHop()`. ```solidity=316 for (uint256 i = 0; i < batches.length; ) { // execute hop, if the whole swap replacing by pmm fails, the funds will return to dexRouter _exeHop(payer, batchesAmount[i], batches[i], extraData); unchecked { ++i; } } ``` This suggests that both arrays `batchesAmount` and `batches` have to be the same length, but there is no check that this is the case.
Open the reportThe function `PMMV2SwapByInvest()` invokes the `_PMMV2Swap()` to perform a token swap via the private market maker. ```solidity=538 function PMMV2SwapByInvest( address receiver, PMMLib.PMMBaseRequest memory baseRequest, PMMLib.PMMSwapRequest calldata request ) external payable nonReentrant returns (uint256 returnAmount) { require(request.fromToken != _ETH, "Invalid source token"); if (baseRequest.fromTokenAmount == 0) { baseRequest.fromTokenAmount = IERC20(request.fromToken).balanceOf(address(this)); } return _PMMV2Swap(address(this), receiver, baseRequest, request); } ``` The argument `fromTokenPayer` is set as the contract address in line 547 of function `PMMV2SwapByInvest()`. ```solidity function _PMMV2Swap( address fromTokenPayer, address receiver, PMMLib.PMMBaseRequest memory baseRequest, PMMLib.PMMSwapRequest calldata request ) internal returns (uint256 returnAmount) { ... ``` However, the `baseRequest.fromTokenAmount` is updated to the `IERC20(request.fromToken).balanceOf(address(this))` only if the input `baseRequest.fromTokenAmount == 0`. In the case that the `baseRequest.fromTokenAmount` is nonzero, it will not be updated. Considering the case the `baseRequest.fromTokenAmount` is larger than `IERC20(request.fromToken).balanceOf(address(this))`, the swap will fail due to insufficient funds in the contract when line 175 of function `_pmmSwapInternal()` is executed. `_pmmSwapInternal()` in **Web3-DEX-dev/contracts/8/PMMRouter.sol** ```solidity=171 if (fromNative) { IWETH(_WETH).deposit{ value: actualAmountRequest }(); IERC20(_WETH).safeTransfer(request.payer, actualAmountRequest); } else if(fromTokenPayer == address(this)) { IERC20(request.fromToken).safeTransfer(request.payer, actualAmountRequest); } else { IApproveProxy(_APPROVE_PROXY).claimTokens(request.fromToken, fromTokenPayer, request.payer, actualAmountRequest); } ```
Open the reportIn the function `_tryPmmSwap()`, the variable `subIndex` is assigned the length of the `bytes` array `pmmRequest.extension`. ```solidity=202 assembly{ subIndex := mload(add(extension, 0x0)) } ``` The operation `add(extension, 0x0)` is redundant meaning `mload(extension)` will produce the same result.
Open the reportIn both functions `_performERC1155Transfer()` and `_performERC721Transfer()`, the variable `success` is assigned with 0 if the passed `token` is not a contract. After that, the `success` is assigned with the return value of the `call()`, which means the `success` is overwritten by the return value of `call()`. In the case that the passed `token` is not a contract, it should return false immediately and should not continue the execution of the remaining code.
Open the reportThe native function `find_program_address` allows to find the address of an account deterministically in base to input seeds (list of values like address, text, or numbers). This address is used by PDAs (Program Derivates Address) accounts. PDAs are managed by the program itself and nobody else. These addresses don't have a private key associated with their address (public key); therefore, no external actors (users or clients) can sign transactions by those addresses. This ensures that only the program owner of the PDA can sign transactions by this account. Finding an address in base to the input seeds that doesn't have a private key is not deterministically and requires a considerable amount of computing power by the on-chain program. The pointed `find_wsol_vault_address` statement is executed always in the successful calls of the instruction `cancel_order`. However, it is useless in the case of a sale order (`order_info.is_buy = false`) because the seller doesn't have a `wsol_vault` account. This leads to unnecessary extra computing units used by the instruction when a sale order is cancelled.
Open the reportThe instruction `close_order` contains two mutually exclusive consecutive `if` statements, this means the if the first one is activated the second one is not activated and the opposite situation. This is less efficient than using a `if else` logic.
Open the reportThe account `rent` is not validated on the instructions `make_order` and `update_price` before calling the `initialize_mint` instruction of the `spl_token` program. This could lead to transactions that fail without a specific error if the rent account is incorrect. The `initialize_mint` instruction on the used version `3.2.0` of the `spl_token` program, doesn't validate that the `rent` account address is correct, but it directly uses a hardcoded value of this address. Therefore, if the passed `rent` account is incorrect, the transaction will fail without a specific error due to a required account (the official `rent` account address) not being passed to the instruction.
Open the reportThe Native Mint account with the name `native_mint_account` is not validated in multiple instructions by comparing with the official Native Mint address. ```rust native_mint_account.key == &spl_token::native_mint::id() ```
Open the reportIn the `update_price` instruction, for buying orders (`order_info.is_buy = true`) the new payment stored on the wrapped SOL account is updated but the delegated amount is not updated. This means that the Vault account (used as the program's wallet) is allowed to manage only the first declared payment calculated using the price defined during the calling of the `make_order` instruction.
Open the reportSome of the packages used on the project are outdated. For example, the `mpl-token-metadata` is on version `1.2.5` but the version `1.3.1` is available, that version improves the deserialization of the Metadata account what is used by this program. The usage of [yanked](https://doc.rust-lang.org/cargo/commands/cargo-yank.html), outdated or vulnerable packages can lead to dangers not visualized on this audit because those packages could insert vulnerabilities on the codebase that were out of the scope, therefore not found.
Open the reportThe System Program account with the name `system_account` is not validated in multiple instructions by comparing with the official System Program ID. ```rust system_account.key == &solana_program::system_program::id() ```
Open the reportThere are typos in multiple files, they are ordered by file and location: - On `instructions.rs` - `accout` should be `account` - `udpate` should be `update` - `expeected` should be `expected` - `Accouts` should be `Accounts` - `protocl` should be `protocol` - On `processor.rs` - `Updata` should be `Update` or `Transfer` to be consistent with the instruction name - `remainning` should be `remaining` - `caclculate` should be `calculate` - `tranfer` should be `transfer` - `exclued` should be `excluded` - `transfered` should be `transferred` - `initilized` should be `initialized` - `Protocl` should be `Protocol` (2) - On `utils.rs` - `Protocl` should be `Protocol` - `dervided` should be `derived` (2) - On `errors.rs` - `Protocl` should be `Protocol`
Open the reportAttached through a shared contract address. Our incident data ends in Oct 2026.
Source: De.Fi incident database.
The reports name a repository or files. Open a report to see its scope.
Is this your project? Show this record on your own website. The label states what the record holds, links to this page and updates by itself. It is free and needs no sign-in.
Automatic: the three strongest facts of this project’s own record (its last audit and two more), highlighted. They update with the record. Picking a fact turns Automatic off.
<head><script src="https://widget.trustblock.run/project-widget.min.js" defer></script></head><body><divdata-tb-project-widgetdata-tb-project-slug="okc-swap"></div></body>
Paste the code where the label should appear. It uses no API credit and updates by itself.
Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.
Tell us