mirror of
https://github.com/sonr-io/crypto.git
synced 2026-09-17 01:26:23 +00:00
391 lines
24 KiB
Plaintext
391 lines
24 KiB
Plaintext
---
|
|
title: Security notes
|
|
description: Critical defects, stubs, non-constant-time paths, and operational footguns found while documenting this library — including three findings that make packages unsafe or unusable as written.
|
|
sidebar:
|
|
label: Security notes
|
|
order: 2
|
|
icon: shield-alert
|
|
---
|
|
|
|
This page records what a source audit turned up while these docs were written. Every claim below was
|
|
verified against the code — most by compiling and running the affected path as an external consumer,
|
|
a few by running the repository's own tests. Where a finding was proven by execution, the observed
|
|
output is quoted.
|
|
|
|
This is not a security audit and does not replace one. Re-check anything critical against the
|
|
version you have vendored, since a defect may be fixed — or a new one introduced — after this page
|
|
was written.
|
|
|
|
:::danger[No audit, no warranty]
|
|
This library has no public third-party security audit. It bundles vendored and ported code from
|
|
several upstream projects, contains packages that are explicitly incomplete, includes arithmetic
|
|
documented in its own comments as not constant time, and — as recorded below — ships at least one
|
|
signature scheme that is trivially forgeable and one persistence path that cannot round-trip.
|
|
:::
|
|
|
|
## Critical
|
|
|
|
Three findings deserve to be read before anything else.
|
|
|
|
### BBS+ signatures are trivially forgeable
|
|
|
|
**`signatures/bbs/message_generators.go` — `MessageGenerators.Get`**
|
|
|
|
The method copies the internal state array, writes the generator index into the **copy**, then hashes
|
|
the **original**:
|
|
|
|
```go
|
|
state := msgg.state // array copy
|
|
state[193] = byte(i >> 24) // index written to the copy
|
|
// ...
|
|
point, ok := msgg.h0.Hash(msgg.state[:]).(curves.PairingPoint) // hashes the ORIGINAL
|
|
```
|
|
|
|
The index never reaches the hash, so every message generator `H_i` for `i >= 1` is the same point.
|
|
A BBS+ signature commits to `h_0^s · Π H_i^{m_i}`; with all `H_i` identical, it binds only the
|
|
**sum** of the message scalars, not the individual messages or their positions.
|
|
|
|
Verified by execution against this repository:
|
|
|
|
```text
|
|
Get(1..4) == Get(0): true
|
|
permuted verifies: true // a signature over [3,4,5,6] is accepted for [6,5,4,3]
|
|
same-sum forgery verifies: true // ...and for the unrelated vector [1,2,7,8]
|
|
```
|
|
|
|
:::danger
|
|
Unforgeability and selective disclosure are both void. Do not use `signatures/bbs` for credentials
|
|
or any authorization decision until `Get` hashes the mutated local copy. See [BBS+](/signatures/bbs).
|
|
:::
|
|
|
|
### A persisted MPC enclave can never be restored
|
|
|
|
**`core/protocol/protocol.go` — `Message.UnmarshalJSON`**
|
|
|
|
`UnmarshalJSON` decodes into `map[string]any` and then performs unchecked type assertions to
|
|
`map[string][]byte` and `map[string]string`. `encoding/json` always produces
|
|
`map[string]interface{}`, so the assertion cannot succeed and the call **panics** on any message
|
|
with a non-empty `Payloads` or `Metadata` field.
|
|
|
|
`mpc.EnclaveData.Marshal` serializes fine, but `Unmarshal` routes through the same decoder. The
|
|
repository's own test fails today:
|
|
|
|
```text
|
|
$ go test ./mpc/ -run TestEnclaveData_MarshalUnmarshal
|
|
panic: interface conversion: interface {} is map[string]interface {}, not map[string][]uint8
|
|
github.com/sonr-io/crypto/mpc.(*EnclaveData).Unmarshal
|
|
mpc/enclave.go:154
|
|
FAIL github.com/sonr-io/crypto/mpc
|
|
```
|
|
|
|
:::danger
|
|
An enclave can be written to storage and never read back. The blast radius is anything that
|
|
JSON-decodes a `protocol.Message` — `DecodeMessage`, `EnclaveData.Unmarshal`, and any transport that
|
|
carries protocol messages as JSON. Persist enclave state through your own encoding until this is
|
|
fixed. See [Protocol messages](/foundations/protocol) and [MPC enclave](/identity/mpc-enclave).
|
|
:::
|
|
|
|
### UCAN caveat and amount attenuation are not enforced
|
|
|
|
**`ucan/verifier.go`**
|
|
|
|
The two helpers that decide whether a delegated token is *more* restrictive than its parent both
|
|
return `true` unconditionally:
|
|
|
|
- `areCaveatsMoreRestrictive(childCaveats, parentCaveats []string) bool` — builds a set of the
|
|
parent's caveats, then runs a loop whose only branch is `continue`, and returns `true`.
|
|
- `isAmountLessOrEqual(childAmount, parentAmount string) bool` — commented
|
|
`placeholder implementation`; the body is `return true`.
|
|
|
|
`isAmountLessOrEqual` gates the `maxAmount` field on a DEX capability. `areCaveatsMoreRestrictive` is
|
|
the final check in vault, DID, and DWN containment validation. Several sibling paths in the same file
|
|
also fail open by design — `return true // Basic containment is sufficient for unknown schemes` —
|
|
so an unrecognized resource scheme is treated as contained rather than rejected.
|
|
|
|
:::danger
|
|
A delegated token can carry caveats, or an amount, that its parent never granted and still pass
|
|
`VerifyDelegationChain`. Do not treat caveat or amount attenuation as a security boundary; enforce
|
|
those constraints in your own application logic. See [UCAN](/identity/ucan).
|
|
:::
|
|
|
|
## Unusable as written
|
|
|
|
APIs that are present and compile, but cannot be used for their stated purpose.
|
|
|
|
### Bulletproof range proofs are uncallable from outside the package
|
|
|
|
`RangeProofGenerators` has only unexported fields (`g`, `h`, `u`) and the package exports no
|
|
constructor, setter, or default. An external package cannot populate it:
|
|
|
|
```text
|
|
cannot refer to unexported field g in struct literal of type bulletproof.RangeProofGenerators
|
|
```
|
|
|
|
A zero-value `RangeProofGenerators{}` does compile, but its points are nil and `RangeProver.Prove`
|
|
panics dereferencing `proofGenerators.h`. The commitment helpers a verifier needs are unexported too
|
|
(`getcapV`, `getcapVBatched`, and `InnerProductProver.getP`, whose own comment says
|
|
*"should only be used for testing"*).
|
|
|
|
Net effect: `RangeProver.Prove`, `BatchProve`, `RangeVerifier.Verify`, and `VerifyBatched` are
|
|
in-package-only. The inner-product argument is usable; the range proof is not. See
|
|
[Bulletproofs](/zero-knowledge/bulletproof).
|
|
|
|
### `sharing/v1.Bls12381G2()` returns a G1 curve
|
|
|
|
```go
|
|
func Bls12381G2() *Bls12381G1Curve {
|
|
bls12381g2Initonce.Do(bls12381g2InitAll)
|
|
return &bls12381g1 // ← the G1 curve
|
|
}
|
|
```
|
|
|
|
The return type is `*Bls12381G1Curve` and the value returned is the package-level `bls12381g1`. The
|
|
G2 initializer runs and its result is discarded; the singleton's `Name` is even set to
|
|
`"Bls12381G1"`. The `Bls12381G2Curve` type does implement real G2 arithmetic, but no exported
|
|
constructor returns it. See [Secret sharing](/threshold/secret-sharing).
|
|
|
|
### `keys.PubKey.Verify` cannot verify this library's own signatures
|
|
|
|
`keys/pubkey.go` requires exactly **66 bytes** laid out as `V || R || S` over a SHA3-256 digest,
|
|
while `mpc.SerializeSignature` emits **64 bytes** as `r || s`. Feeding one to the other yields
|
|
`malformed signature: not the correct size`. Separately, `getEcdsaPoint` slices `y = bytes[33:]` from
|
|
a compressed 33-byte point (`Point.Bytes()` always returns compressed), so `y` decodes as zero. No
|
|
test covers `NewPubKey` or `Verify`. See [did:key](/identity/did-key).
|
|
|
|
### `mina.Transaction.UnmarshalJSON` always fails
|
|
|
|
It type-asserts `Body[1]` from `any` directly to concrete struct types. `encoding/json` decodes an
|
|
unconstrained `any` into `map[string]any` / `[]any`, so the assertion can never succeed and every
|
|
call returns `unexpected type`. Even if the assertion were fixed, `SourcePk`, `Amount`, `TokenId`,
|
|
`Locked`, and `Tag` are never assigned, a computed `sourcePk` local is dropped, a `ParseAddress`
|
|
error is swallowed with `return nil`, and the memo is indexed `memo[2 : 2+memo[1]]` with no length
|
|
check. There is no `MarshalJSON` counterpart. See [Chain schemes](/signatures/chain-schemes).
|
|
|
|
### `keys/parsers` is a skeleton
|
|
|
|
Five files contain nothing but a package clause: `btc_parser.go`, `eth_parser.go`, `fil_parser.go`,
|
|
`sol_parser.go`, `ton_parser.go`. There is no Bitcoin, Ethereum, Filecoin, Solana, or TON key parsing
|
|
in this module. `cosmos_parser.go` holds only `CosmosPrefix` HRP constants, with no functions.
|
|
|
|
`keys/parsers/key_parser.go` also duplicates `keys/didkey.go` but with a **different secp256k1
|
|
multicodec** — `0x1206` against the registered `0xe7` used by `keys` — so `parsers.DIDKey` and
|
|
`keys.DID` produce mutually unparseable `did:key` strings for the same key.
|
|
|
|
:::warning
|
|
Use `keys`. Treat `keys/parsers` as dead code.
|
|
:::
|
|
|
|
### `ucan/stubs.go`
|
|
|
|
`TokenBuilder.CreateOriginToken` and `CreateDelegatedToken` assemble a `*Token` with `Raw: ""` — they
|
|
never sign or serialize a JWT. `isValidDID` checks only a `did:` prefix and a length, and
|
|
`prepareDelegationProofs` merely copies the parent's `Raw` when non-empty.
|
|
|
|
For a signed token use `GenerateJWTToken`, `GenerateModuleJWTToken`, or the MPC-backed
|
|
`MPCTokenBuilder` — not the bare `TokenBuilder`.
|
|
|
|
### `empty-module`
|
|
|
|
A separate Go module declaring itself `github.com/tyler-smith/go-bip39`, whose functions all return
|
|
an error or panic. Neither `go.mod` nor `go.sum` references it and there is no `go.work`, so nothing
|
|
builds against it. There is no BIP-39 mnemonic support in this library.
|
|
|
|
### `core/curves/native/pasta/pallas.go`
|
|
|
|
Contains only a package clause. Working Pallas support lives in `core/curves/pallas_curve.go`
|
|
(`PointPallas`, `ScalarPallas`, `Ep`).
|
|
|
|
## Silent wrong answers
|
|
|
|
Code that runs, returns no error, and is wrong.
|
|
|
|
| Finding | Location | Consequence |
|
|
| --- | --- | --- |
|
|
| FROST DKG context is discarded | `dkg/frost/participant.go` — `ctxV, _ := strconv.Atoi(ctx)`, stored as `byte(ctxV)` | The error is dropped, so any non-numeric context — including the package's own test string — becomes the byte `0`. Every such session shares one context, and numeric values are truncated mod 256. The replay-protection domain separator does nothing as implemented. Participant ids `>= 256` truncate the same way. Inherited by `ted25519/frost`. |
|
|
| `v1.Shamir.Combine` truncates | `sharing/v1/shamir.go` | Only the first `threshold` shares are consumed; extra shares are silently ignored rather than cross-checked. |
|
|
| Hard-coded hash-to-field DST | `core/hash.go` — `hashToField` | The domain separation tag is the literal `Coinbase_tECDSA` with no parameter. No separation between protocols, and no interoperability with any standard hash-to-curve suite ID. |
|
|
| Fixed Fiat-Shamir info string | `core/hash.go` — `FiatShamir` | `info` is the literal `Coinbase tECDSA 1.0` with a 32-byte zero salt. Values are folded as minimal big-endian `Bytes()`, so lengths are not committed — two different value sequences can produce one transcript. |
|
|
| `keys.DID.Address()` is not an address | `keys/didkey.go` | The comment claims an Ethereum-style Keccak-256 truncation; the code is `fmt.Sprintf("sonr1%x", rawPubBytes[:8])` for all key types. No hash, no bech32, no checksum. It leaks 8 bytes of the public key into a 64-bit collision space. Measured: `sonr10304584a69c0f8ac`. Consumed by `ucan` via `MPCTokenBuilder.GetAddress()` and `KeyshareSource.Address()`. |
|
|
| Mina threshold challenge is MainNet-only | `signatures/schnorr/mina/challenge_derive.go` | `DeriveChallenge` hard-codes `MainNet` after parsing a `Transaction` that carries a `NetworkId`, then discards it. FROST-signing a TestNet transaction produces a signature that will not verify. No override is exposed. |
|
|
| Mina memo length corruption | `signatures/schnorr/mina/txn.go` — `MarshalBinary` | Writes `out[57] = byte(len(txn.Memo))` but copies at most 32 bytes. A 40-byte memo records length 40 with 32 bytes present; a 256-byte memo records length 0. Also dereferences `FeePayerPk`/`SourcePk`/`ReceiverPk` with no nil checks, so a partially filled `Transaction` panics. |
|
|
| `NistP256.ScalarMult` is not the native path | `core/curves/p256_curve.go` | The method is spelled `ScalarMul` (missing `t`), so the `elliptic.Curve` interface method resolves to the promoted `*elliptic.CurveParams.ScalarMult` — the generic deprecated `math/big` implementation. `ScalarBaseMult`, `Add`, `Double`, and `IsOnCurve` are native. |
|
|
| `BLS12831Name` typo is load-bearing | `core/curves/curve.go` | The constant is spelled `BLS12831` **and** its value is the string `"BLS12831"`. `curves.BLS12381(...)` assigns it, so a BLS12-381 pairing curve reports `Name == "BLS12831"`. Any name-based dispatch must match the typo. |
|
|
| `core.Add`/`Mul`/`Exp` accept a nil modulus | `core/mod.go` | A nil modulus means no reduction rather than an error, so a missing parameter silently yields unreduced big integers. |
|
|
| `Iterator.Result` returns `(nil, nil)` | `tecdsa/dklsv1/boilerplate.go`, all six `Result` methods | The completion check precedes the `ErrNotInitialized` check, so calling `Result` on an un-cranked iterator returns a nil message *and* a nil error. Every `Decode*` helper then nil-derefs on `m.Payloads`. |
|
|
| `Point.SumOfProducts` signals failure with nil | `core/curves/k256_curve.go` and siblings | No error channel. Returns nil on a slice-length mismatch or on any element of a foreign concrete type, turning a length bug into a nil-deref several frames later. |
|
|
| `Curve.ToEllipticCurve` covers 2 of 8 curves | `core/curves/curve.go` | Only `K256` and `P256` convert; `ED25519`, `PALLAS`, and all four BLS variants return nil with `can't convert <name>`. |
|
|
| `daed.AESSIV` aliases the caller's key | `daed/aes_siv.go` | `K1`/`K2` are **exported** fields that alias `key[:32]` and `key[32:]` rather than copying. `fmt.Printf("%+v")` on an `AESSIV` prints raw key material, and zeroing the input slice silently corrupts the live cipher. |
|
|
| `daed` decrypt ignores an error | `daed/aes_siv.go` | `DecryptDeterministically` calls `ctrCrypt` without checking its returned error, unlike the encrypt path. Latent rather than exploitable, since `ctrCrypt` can only fail if `aes.NewCipher(K2)` fails after the constructor's 64-byte check. |
|
|
| `mpc/spec` duplicates `ucan` | `mpc/spec/` | A near-verbatim fork of `ucan/source.go` and `ucan/mpc.go` with its own `Token`, `Capability`, and `Attenuation` types. Two copies of authorization logic drift apart. `mpc/spec/source.go` also derives its address from the placeholder `fmt.Sprintf("addr_%x", pubKeyBytes[:8])`. Prefer `ucan`. |
|
|
|
|
### Bulletproof range-encoding edge cases
|
|
|
|
Beyond being uncallable externally, the range prover has four issues worth recording if it is ever
|
|
fixed or used in-package:
|
|
|
|
- `getaL` reads bit `i` as `vBytes[i>>3]` with no bounds check, so `n > 256` on these curves indexes
|
|
past the slice and panics. `NewRangeProver` accepts `maxVectorLength` above 256 with no gate.
|
|
- `getaL` assumes `Scalar.Bytes()` is little-endian. Every bulletproof test uses ED25519 only; on a
|
|
big-endian-scalar curve the bit vector is reversed and will not match the commitment.
|
|
- `Prove` rejects `v < 0` and `v > 2^n`, so `v == 2^n` passes validation but is not representable in
|
|
`n` bits. The unexported `checkRange` used by `BatchProve` has the same comparison despite a
|
|
comment claiming `[0, 2^n - 1]`, and additionally omits the negative check.
|
|
- `n` must be a power of two, but `RangeProver.Prove` has no gate (unlike
|
|
`InnerProductProver.Prove`), so a bad `n` fails late inside the recursion with
|
|
`length of scalars must be even`.
|
|
- `Verify` and `VerifyBatched` return `(false, nil)` with no diagnostic, so a domain,
|
|
`maxVectorLength`, generator, or transcript-label mismatch is indistinguishable from a dishonest
|
|
prover.
|
|
|
|
## Non-constant-time arithmetic
|
|
|
|
The following are documented as not constant time **in their own source comments**:
|
|
|
|
| Location | Note |
|
|
| --- | --- |
|
|
| `core/curves/field.go` | `Field` and `Element` are `math/big`-backed and explicitly documented as not constant time. `NewField` and the element constructor **panic** on a non-prime modulus, an out-of-range value, or mismatched fields. |
|
|
| `core/curves/ec_scalar.go` | The `big.Int` Euclidean `Mod` path is flagged as not constant time. Affects `K256Scalar`, `P256Scalar`, `Bls12381Scalar`, and `Ed25519Scalar`. |
|
|
| `core` modular helpers | `Add`, `Mul`, `Exp`, `Inv`, `Neg` operate on `*big.Int`. Use `ConstantTimeEq` for comparisons and do not assume the arithmetic itself is constant time. |
|
|
|
|
The modern `curves.Point` / `curves.Scalar` implementations backed by `core/curves/native`
|
|
(Montgomery-form limb arithmetic) are the better choice for secret-dependent operations. The legacy
|
|
`Field` / `Element` / `EcScalar` layer is used by `sharing/v1` and `dkg/gennaro`, which inherit its
|
|
timing characteristics.
|
|
|
|
## Operational footguns
|
|
|
|
Not bugs — the code does what it says — but each has a severe failure mode.
|
|
|
|
<Accordion>
|
|
<AccordionItem title="Nonce reuse in threshold Ed25519 reveals the signing key" icon="triangle-alert">
|
|
A nonce share from `GenerateSharedNonce` is bound to one message. Signing two different messages
|
|
with the same nonce share exposes the secret key through simple algebra. Generate a fresh nonce
|
|
per signing session; never persist and replay one. See
|
|
[Threshold Ed25519](/threshold/threshold-ed25519).
|
|
</AccordionItem>
|
|
<AccordionItem title="AES-GCM with a caller-supplied nonce" icon="triangle-alert">
|
|
`aead.AESGCMCipher.EncryptWithNonce` exists for test vectors, and its own source comment says
|
|
"use only for testing". Repeating a nonce under one key destroys both confidentiality (CTR
|
|
keystream reuse) and authenticity (GHASH subkey leakage, enabling forgeries for other messages).
|
|
`Encrypt` generates a random 96-bit nonce and prepends it — use that. See [AEAD](/symmetric/aead).
|
|
</AccordionItem>
|
|
<AccordionItem title="The MPC enclave holds both shares in one process" icon="triangle-alert">
|
|
`NewEnclave` runs both DKLs18 DKG sides locally, `EnclaveData` stores `ValShare` and `UserShare`
|
|
together, `Sign` builds both sign functions from the same struct, and `Marshal` emits both in the
|
|
clear. It is a key-management and portability construct; the threshold property only materializes
|
|
once the two shares live in separate trust domains. See [MPC enclave](/identity/mpc-enclave).
|
|
</AccordionItem>
|
|
<AccordionItem title="Enclave encryption uses a fixed per-enclave nonce" icon="triangle-alert">
|
|
`EnclaveData.Encrypt` derives an AES-256-GCM key with SHA3-256 and reuses the enclave's stored
|
|
nonce, which is the AES-GCM failure case above whenever more than one plaintext is encrypted.
|
|
</AccordionItem>
|
|
<AccordionItem title="The trusted dealer defeats the point of DKG" icon="triangle-alert">
|
|
`tecdsa/dklsv1/dealer.GenerateAndDeal` constructs both parties' shares in one process, so the
|
|
full key exists in one place at one time. It is a test and migration convenience. See
|
|
[Threshold ECDSA](/threshold/threshold-ecdsa).
|
|
</AccordionItem>
|
|
<AccordionItem title="Session ids must be unique per protocol execution" icon="triangle-alert">
|
|
`zkp/schnorr`, `ot/base/simplest`, and the FROST DKG all take a session id or context that
|
|
domain-separates the Fiat-Shamir transcript. Prover and verifier must pass identical bytes, and
|
|
reuse across executions weakens the soundness the caller assumes. Note the FROST context defect
|
|
above. See [Schnorr proofs](/zero-knowledge/schnorr).
|
|
</AccordionItem>
|
|
<AccordionItem title="Accumulator witnesses go stale on every update" icon="triangle-alert">
|
|
Adding or removing an element invalidates every outstanding membership witness. Holders must
|
|
refresh via `ApplyDelta` or `BatchUpdate` using the published `Delta`, or their proofs stop
|
|
verifying with the bare error `invalid result`. A revoked holder's `BatchUpdate` fails with
|
|
`no inverse exists`. See [Accumulator](/zero-knowledge/accumulator).
|
|
</AccordionItem>
|
|
<AccordionItem title="Shamir sharing does not detect a corrupted share" icon="triangle-alert">
|
|
Plain `sharing.Shamir` has no verification step, so a malicious holder can submit a garbage share
|
|
and silently corrupt the reconstructed secret. Use Feldman or Pedersen when holders are not
|
|
trusted. See [Secret sharing](/threshold/secret-sharing).
|
|
</AccordionItem>
|
|
<AccordionItem title="BLS Basic and Aug do not stop rogue-key attacks" icon="triangle-alert">
|
|
Only the proof-of-possession ciphersuite (`SigPop`, `SigPopVt`, and the `SigEth2` aliases)
|
|
defends against an attacker registering a public key derived from others'. Basic additionally
|
|
requires every message in an aggregate to be distinct. See [BLS](/signatures/bls).
|
|
</AccordionItem>
|
|
<AccordionItem title="Deterministic AEAD leaks plaintext equality" icon="triangle-alert">
|
|
`daed` produces identical ciphertext for identical plaintext and associated data. That is the
|
|
feature, but an observer learns which ciphertexts encrypt the same value, can join across tables,
|
|
and can confirm guesses offline. See [Deterministic AEAD](/symmetric/deterministic-aead).
|
|
</AccordionItem>
|
|
<AccordionItem title="A short PsfProof panics instead of erroring" icon="triangle-alert">
|
|
`PsfProof.Verify` indexes the proof without a length check:
|
|
`index out of range [3] with length 3`. Validate that a deserialized proof has `PsfProofLength`
|
|
elements before verifying. See [Paillier](/zero-knowledge/paillier).
|
|
</AccordionItem>
|
|
<AccordionItem title="Ciphertexts carry no algorithm or key identifier" icon="triangle-alert">
|
|
`aead` output is `nonce || ciphertext || tag` with no version byte, algorithm id, or key id.
|
|
There is no key-rotation or migration path short of re-encrypting everything.
|
|
</AccordionItem>
|
|
</Accordion>
|
|
|
|
## Weaker guarantees than the names suggest
|
|
|
|
### `secure` does not lock memory
|
|
|
|
`secure/memory.go` overwrites buffers and registers finalizers. It contains no `mlock`, `munlock`,
|
|
or `mprotect` call, so secrets remain swappable to disk and readable from a core dump.
|
|
`ZeroizeString` cannot work reliably at all: Go strings are immutable and freely copied, so the copy
|
|
you zero may not be the only one. Treat these as hygiene, not a guarantee. See
|
|
[Secrets](/symmetric/secrets).
|
|
|
|
### `subtle/random` panics instead of returning an error
|
|
|
|
`GetRandomBytes` and `GetRandomUint32` panic if `crypto/rand` fails rather than surfacing an error —
|
|
a process crash originating in library code.
|
|
|
|
### `salt.SaltStore` is not goroutine-safe
|
|
|
|
An in-memory map with no mutex. Concurrent `Store` and `Retrieve` calls race. Serialize access
|
|
yourself.
|
|
|
|
### `ecies` has no round-trip test
|
|
|
|
A thin alias layer over `github.com/ecies/go/v2`. Its test file covers key generation only — no
|
|
encrypt/decrypt round trip is exercised in this repository. See [ECIES](/identity/ecies).
|
|
|
|
### `daed` cross-implementation vectors never run
|
|
|
|
`TestAESSIV_WycheproofVectors` calls `t.Skip` unless `TEST_SRCDIR` is set, so a normal
|
|
`go test ./daed/...` never checks the RFC 5297 vectors.
|
|
|
|
### `wasm.Signer.ExportPrivateKey`
|
|
|
|
Returns raw Ed25519 private key bytes, so any caller holding a `*Signer` can extract the signing key.
|
|
See [WASM modules](/identity/wasm-modules).
|
|
|
|
### `keys.DID` error handling
|
|
|
|
`MulticodecType()` panics with `unexpected crypto type` on an unguarded key type, and `String()`
|
|
calls it unconditionally — so a `DID` built as a struct literal can panic. `String()` also returns
|
|
`""` instead of an error when `Raw()` or multibase encoding fails.
|
|
|
|
## What the repository does test
|
|
|
|
`security_test.go` at the module root is a cross-package suite asserting properties rather than
|
|
units. It is a useful statement of intended guarantees:
|
|
|
|
- Argon2 timing behavior under configured cost, and concurrent derivation safety
|
|
- ECDSA signing determinism and rejection of malleable (high-S) signatures
|
|
- Password validator resistance to dictionary inputs
|
|
- WASM module hash collision resistance
|
|
- Salt uniqueness across generations
|
|
- RNG output quality
|
|
- Crypto agility across configured algorithms
|
|
|
|
```bash
|
|
go test ./... -run TestSecurity
|
|
```
|
|
|
|
Note that `go test ./mpc/` currently fails on `TestEnclaveData_MarshalUnmarshal` for the reason
|
|
recorded above.
|
|
|
|
## Reporting
|
|
|
|
Found something not listed here? Open an issue at
|
|
[github.com/sonr-io/crypto](https://github.com/sonr-io/crypto/issues). For a suspected
|
|
vulnerability, prefer a private report over a public issue.
|