Driver Implementation: token/core/zkatdlog/nogh/v1
Driver Name: zkatdlognogh
Protocol Version: 1
Date: 2026-03-25
The Zero-Knowledge Authenticated Token based on Discrete Logarithm (ZKAT-DLOG) driver, specifically the No Graph Hiding (NOGH) variant, is a privacy-preserving token implementation for Panurus. It leverages Zero-Knowledge Proofs (ZKP) to hide token types and values while revealing the spending graph.
Implementation Path: token/core/zkatdlog/nogh/v1
ZKAT-DLOG (NOGH) provides a balance between privacy and efficiency:
The ZKAT-DLOG (NOGH) driver provides enhanced privacy compared to the FabToken driver:
| Feature | FabToken | ZKAT-DLOG (NOGH) |
|---|---|---|
| Value Privacy | ❌ Cleartext | ✅ Hidden (Pedersen Commitments) |
| Type Privacy | ❌ Cleartext | ✅ Hidden (Pedersen Commitments) |
| Owner Privacy | ❌ X.509 (Visible) | ✅ Idemix Pseudonyms (Anonymous) |
| Graph Privacy | ❌ Visible | ❌ Visible (NOGH variant) |
| Auditor Support | ✅ Yes | ✅ Yes (with de-anonymization) |
| Performance | Fast | Moderate (ZK proof overhead) |
| Proof Size | N/A | ~2KB per transfer |
| Identity Type | X.509 only | Idemix (owners), X.509 (issuers/auditors) |
| Upgrade Support | N/A | ✅ Can spend FabToken in-place |
When to Use ZKAT-DLOG (NOGH):
When to Use FabToken:
The scheme is secure under computational assumptions in bilinear groups in the random-oracle model, following the blueprint described in the paper Privacy-preserving auditable token payments in a permissioned blockchain system by Elli Androulaki et al.
graph TB
subgraph "Token Lifecycle"
A[Issue] --> B[Transfer]
B --> C[Transfer]
C --> D[Redeem]
end
subgraph "Participants"
I[Issuer<br/>X.509]
O1[Owner 1<br/>Idemix]
O2[Owner 2<br/>Idemix]
A1[Auditor<br/>X.509]
end
I -->|Creates| A
O1 -->|Spends| B
O2 -->|Spends| C
I -->|Authorizes| D
A1 -.->|Audits| B
A1 -.->|Audits| C
The protocol is instantiated over a pairing-friendly elliptic curve $\mathcal{C}$ (default: BLS12-381).
Supported Curves:
BN254 (Barreto-Naehrig) - Faster operations, suitable for development and testingBLS12_381_BBS (BLS12-381 with BBS+ signatures) - Better security margins, recommended for productionNote on Curve Variants: The implementation supports multiple BLS12-381 variants:
BLS12_381_BBS: Standard implementation with BBS+ signaturesBLS12_381_BBS_GURVY: Gurvy library implementationBLS12_381_BBS_GURVY_FAST_RNG: Optimized variant with faster random number generation (for testing)For production deployments, use BLS12_381_BBS.
"zkat-dlog.nogh.g0").The Pedersen generators are derived deterministically using cryptographically secure random number generation.
Implementation: See setup.go for the complete generator derivation logic.
The public parameters include three Pedersen generators [g_0, g_1, g_2] used for commitment schemes:
g_0: Used for token type commitmentsg_1: Used for token value commitmentsg_2: Used for blinding factor commitmentsAs of commit 586d4f58, the driver supports two range proof systems:
crypto/rp/bulletproof/crypto/rp/csp/The proof system is selected via the ProofType parameter in SetupParams:
rp.Bulletproof - Uses Bulletproof range proofs (default)rp.CSP - Uses Compressed Sigma Protocol range proofsPerformance Comparison: CSP proofs offer improved verification performance through optimized Lagrange interpolation, particularly beneficial for high-throughput scenarios. See benchmark documentation for detailed performance metrics.
The driver implements the Driver API through several specialized services following a consistent architectural pattern.
graph TB
subgraph "Driver Layer"
D[Driver<br/>driver.go]
end
subgraph "Service Layer"
S[Service<br/>service.go]
IS[IssueService<br/>issue.go]
TS[TransferService<br/>transfer.go]
AS[AuditorService<br/>auditor.go]
TKS[TokensService<br/>token/]
end
subgraph "Core Logic"
I[Issue<br/>issue/]
T[Transfer<br/>transfer/]
A[Audit<br/>audit/]
V[Validator<br/>validator/]
end
subgraph "Crypto Layer"
RP[Range Proofs<br/>crypto/rp/]
UP[Upgrade<br/>crypto/upgrade/]
end
subgraph "Foundation"
PP[PublicParams<br/>setup/]
TK[Token<br/>token/]
end
D --> S
S --> IS
S --> TS
S --> AS
S --> TKS
IS --> I
TS --> T
AS --> A
S --> V
I --> RP
T --> RP
T --> UP
I --> PP
T --> PP
A --> PP
V --> PP
I --> TK
T --> TK
A --> TK
The driver leverages several token services (see @/token/services):
The PublicParams (v1) define the cryptographic environment:
type PublicParams struct {
// DriverName is the name of token driver this public params refer to.
DriverName driver.TokenDriverName // "zkatdlognogh"
// DriverVersion is the version of token driver this public params refer to.
DriverVersion driver.TokenDriverVersion // 1
// Curve is the pairing-friendly elliptic curve used for everything but Idemix.
Curve mathlib.CurveID
// PedersenGenerators contains the public parameters for the Pedersen commitment scheme.
PedersenGenerators []*mathlib.G1 // [g_0, g_1, g_2]
// RangeProofParams contains the public parameters for the range proof scheme.
RangeProofParams *RangeProofParams
// IdemixIssuerPublicKeys contains the idemix issuer public keys
IdemixIssuerPublicKeys []*IdemixIssuerPublicKey
// Auditors is a list of the public keys of the auditor(s).
AuditorIDs []driver.Identity
// IssuerIDs is a list of public keys of the entities that can issue tokens.
IssuerIDs []driver.Identity
// MaxToken is the maximum quantity a token can hold
MaxToken uint64
// QuantityPrecision is the precision used to represent quantities
QuantityPrecision uint64 // 16, 32, or 64 bits
// ExtraData contains any extra custom data
ExtraData driver.Extras
}
The driver supports two types of range proof parameters depending on the selected proof system:
type RangeProofParams struct {
LeftGenerators []*mathlib.G1 // Length = BitLength
RightGenerators []*mathlib.G1 // Length = BitLength
P *mathlib.G1 // Base point for IPA
Q *mathlib.G1 // Base point for IPA
BitLength uint64 // Arbitrary number between 1 and 64 (included)
NumberOfRounds uint64 // log2(BitLength)
}
type CSPRangeProofParams struct {
LeftGenerators []*mathlib.G1 // Length = BitLength + 1
RightGenerators []*mathlib.G1 // Length = BitLength + 1
BitLength uint64 // Arbitrary number between 1 and 64 (included)
}
Key Differences:
BitLength + 1 (one extra generator for the protocol)Selection: The appropriate parameter structure is populated based on the ProofType specified during setup. Only one set of parameters is included in the serialized PublicParams.
Supported Precisions: Any number between 1 and 64 is accepted. For the CSP-based range proof, since CSP inner product argument is over 2n+4 sized vector, bit lengths of 30 or 62 are ideal because it makes 2n+4 a power of 2, i.e, 64 and 128 respectively. This avoids performance hit from overflowing to next power of two. Sacrificing 2 bits from the range is perhaps worth the performance. This is ultimately a decision that must be driven by the requirements of the specific use-case.
The precision determines both the maximum token value and the size of the range proofs. Higher precision allows larger token values but results in larger proofs and slower verification.
The driver provides a mechanism to instantiate a TokenManagementService (TMS) tailored to the network, channel, and namespace:
sequenceDiagram
participant C as Client
participant D as Driver
participant PPM as PublicParamsManager
participant WS as WalletService
participant S as Service
C->>D: NewTokenService(tmsID, publicParams)
D->>PPM: NewPublicParamsManager(publicParams)
PPM-->>D: ppm
D->>WS: NewWalletService(config, ppm)
WS-->>D: ws
D->>S: NewTokenService(ws, ppm, ...)
S-->>D: service
D-->>C: TokenManagerService
ZKAT-DLOG supports a hybrid identity model managed by the Identity Service.
Idemix provides anonymity through a three-tier structure:
graph LR
subgraph "Idemix Identity Hierarchy"
E[Enrollment ID<br/>Long-term] --> C[Credential<br/>A, e, v]
C --> N[Pseudonym<br/>N = g^sk · h^r]
end
subgraph "On-Ledger"
N --> T[Token Owner]
end
ZKAT-DLOG supports two Idemix formats:
owner field contains a full Idemix signature.owner field contains a small Nym EID (commitment to Enrollment ID). See IdemixNym spec.To spend, the owner provides a Signature of Knowledge (SoK) proving they know $sk$ and hold a valid credential without revealing them.
Issuers and Auditors are identified by standard X.509 certificates.
PublicParams.IssuerIDs.PublicParams.AuditorIDs.All identities are wrapped in a TypedIdentity (ASN.1 encoded), prefixing the payload with a type label (e.g., "idemix", "x509", "ms", "htlc").
// Identity types supported
const (
IdemixIdentityType = "idemix"
IdemixNymIdentityType = "idemixnym"
X509IdentityType = "x509"
HTLCIdentityType = "htlc"
MultisigIdentityType = "ms"
)
A token $(T, V)$ with blinding factor $r \in_R \mathbb{Z}_r$ is committed as:
\[\text{Comm}(T, V; r) = g_0^{H_{Type}(T)} \cdot g_1^V \cdot g_2^r\]Implementation:
// From token/token.go
func commit(vector []*math.Zr, generators []*math.G1, c *math.Curve) (*math.G1, error) {
com := c.NewG1()
for i := range vector {
if vector[i] == nil {
return nil, ErrNilCommitElement
}
com.Add(generators[i].Mul(vector[i]))
}
return com, nil
}
To prove type consistency in transfers without revealing the type:
\[\text{CommType}(T; r_T) = g_0^{H_{Type}(T)} \cdot g_2^{r_T}\]This allows proving that multiple commitments share the same type without revealing $T$.
// Token represents a ZKAT-DLOG token
type Token struct {
Owner []byte // Serialized identity (Idemix or X.509)
Data *math.G1 // Pedersen commitment: g_0^H(T) · g_1^V · g_2^r
}
// Metadata contains the opening information
type Metadata struct {
Type token.Type // Token type (e.g., "USD")
Value *math.Zr // Token value
BlindingFactor *math.Zr // Blinding factor r
Issuer []byte // Issuer identity (for issue actions)
}
TypeAndSumProof)This proof demonstrates that for inputs ${C_{in,1}, \dots, C_{in,m}}$ and outputs ${C_{out,1}, \dots, C_{out,k}}$:
sequenceDiagram
participant P as Prover
participant V as Verifier
Note over P: 1. Commit to Randomness
P->>P: Pick s_T, s_{r,T}, s_{bf,i}, s_{v,i}, s_{sum}
P->>P: A_type = g_0^{s_T} · g_2^{s_{r,T}}
P->>P: A_{in,i} = g_1^{s_{v,i}} · g_2^{s_{bf,i}}
P->>P: A_sum = g_2^{s_sum}
Note over P: 2. Compute Challenge
P->>P: c = H_FS(..., A_type, {A_{in,i}}, A_sum)
Note over P: 3. Compute Responses
P->>P: z_T = s_T + c · H_Type(T)
P->>P: z_{r,T} = s_{r,T} + c · r_T
P->>P: z_{v,i} = s_{v,i} + c · v_{in,i}
P->>P: z_{bf,i} = s_{bf,i} + c · (r_{in,i} - r_T)
P->>P: z_sum = s_sum + c · Σ((r_{in,i} - r_T) - (r_{out,j} - r_T))
P->>V: Send (c, z_T, z_{r,T}, {z_{v,i}}, {z_{bf,i}}, z_sum)
Note over V: Verify proof
V->>V: Recompute A_type', A_{in,i}', A_sum'
V->>V: c' = H_FS(..., A_type', {A_{in,i}'}, A_sum')
V->>V: Check c == c'
Prover Steps:
Challenge: $c = H_{FS}(\dots, A_{type}, {A_{in,i}}, A_{sum})$
The driver supports two range proof systems. The choice is made during public parameter setup and affects proof generation and verification.
Each output $C_{out,j}$ includes a Bulletproof showing $V_j \in [0, 2^{64}-1]$. It uses an Inner Product Argument (IPA) to achieve $O(\log n)$ proof size.
Implementation: crypto/rp/bulletproof/
Bulletproof Structure:
type RangeProof struct {
Commitments []*math.G1 // Bit commitments
A *math.G1 // Aggregated commitment
S *math.G1 // Blinding commitment
T1 *math.G1 // Polynomial commitment 1
T2 *math.G1 // Polynomial commitment 2
Tau *math.Zr // Blinding factor
Mu *math.Zr // Blinding factor
IPAProof *IPAProof // Inner product argument
}
Performance:
New in commit 586d4f58: CSP-based range proofs using recursive folding with Fiat-Shamir transformation.
Implementation: crypto/rp/csp/
CSP Proof Structure:
type CSPProof struct {
Left []*mathlib.G1 // Cross-commitment MSM(gen_L, wit_R)
Right []*mathlib.G1 // Cross-commitment MSM(gen_R, wit_L)
VLeft []*mathlib.Zr // Cross scalar ⟨f_L, wit_R⟩
VRight []*mathlib.Zr // Cross scalar ⟨f_R, wit_L⟩
Curve *mathlib.Curve
}
Protocol Overview:
At each of the NumberOfRounds folding steps, the prover supplies:
Left[i] = MSM(gen_L, wit_R) — cross-commitmentRight[i] = MSM(gen_R, wit_L) — cross-commitmentVLeft[i] = ⟨f_L, wit_R⟩ — cross scalarVRight[i] = ⟨f_R, wit_L⟩ — cross scalarThe verifier reproduces Fiat-Shamir challenges and performs final verification using Lagrange interpolation.
Performance:
Key Advantages:
Implementation: issue.go, issue/
Orchestrates new token creation.
type IssueAction struct {
Issuer []byte // X.509 identity of issuer
Outputs []*Token // New token commitments
Proof *IssueProof // ZKP of validity
Inputs []*ActionInput // Optional: tokens to redeem (upgrade)
Metadata []byte // Application-specific data
}
sequenceDiagram
participant C as Client
participant IS as IssueService
participant I as Issuer
participant V as Verifier
C->>IS: Issue(issuerID, type, values, owners)
IS->>I: NewIssuer(type, issuerID, pp)
I->>I: GenerateZKIssue(values, owners)
Note over I: 1. Generate blinding factors
Note over I: 2. Compute commitments
Note over I: 3. Generate range proofs
Note over I: 4. Generate type proof
I-->>IS: (IssueAction, Metadata)
IS->>IS: Prepare audit metadata
IS-->>C: (IssueAction, IssueMetadata)
Note over C: Submit to network
C->>V: Verify(IssueAction, Metadata)
V->>V: Verify range proofs
V->>V: Verify type consistency
V->>V: Verify issuer signature
V-->>C: Valid/Invalid
Transient Metadata (not stored on-ledger):
On-Ledger Metadata:
Implementation: transfer.go, transfer/
Manages ownership movement.
type TransferAction struct {
Inputs []*ActionInput // Input token IDs and commitments
Outputs []*Token // New token commitments
Proof *TransferProof // TypeAndSumProof + RangeProofs
Issuer []byte // Optional: for redemption
Metadata []byte // Application-specific data
}
type ActionInput struct {
ID *token.ID // (TxID, Index)
Token *Token // Input commitment
UpgradeWitness *UpgradeWitness // Optional: for FabToken upgrade
}
sequenceDiagram
participant C as Client
participant TS as TransferService
participant TL as TokenLoader
participant S as Sender
participant V as Validator
C->>TS: Transfer(ids, outputs)
TS->>TL: LoadTokens(ids)
TL-->>TS: LoadedTokens
TS->>TS: prepareInputs(LoadedTokens)
TS->>S: NewSender(inputs, ids, metadata, pp)
S->>S: GenerateZKTransfer(values, owners)
Note over S: 1. Compute type commitment
Note over S: 2. Generate TypeAndSumProof
Note over S: 3. Generate range proofs for outputs
S-->>TS: (TransferAction, OutputMetadata)
TS->>TS: Prepare audit metadata
TS-->>C: (TransferAction, TransferMetadata)
Note over C: Submit to network
C->>V: Verify(TransferAction, Metadata)
V->>V: Load input commitments
V->>V: Verify TypeAndSumProof
V->>V: Verify range proofs
V->>V: Verify owner signatures (SoK)
V-->>C: Valid/Invalid
The transfer service loads tokens from the vault and deserializes them:
func (s *TransferService) prepareInputs(ctx context.Context, loadedTokens []LoadedToken) (PreparedTransferInputs, error) {
preparedInputs := make([]PreparedTransferInput, len(loadedTokens))
for i, loadedToken := range loadedTokens {
tok, tokenMetadata, upgradeWitness, err := s.TokenDeserializer.DeserializeToken(
ctx,
loadedToken.TokenFormat,
loadedToken.Token,
loadedToken.Metadata,
)
if err != nil {
return nil, errors.Wrapf(err, "failed deserializing token")
}
preparedInputs[i] = PreparedTransferInput{
Token: tok,
Metadata: tokenMetadata,
Owner: tok.GetOwner(),
UpgradeWitness: upgradeWitness,
}
}
return preparedInputs, nil
}
Implementation: token/tokens.go
Provides token deserialization and format conversion.
zkatdlog: Native ZKAT-DLOG commitmentsfabtoken: Cleartext FabToken (for backward compatibility and upgrades)Reveals cleartext via metadata openings:
func (t *Token) ToClear(meta *Metadata, pp *PublicParams) (*token.Token, error) {
// Recompute commitment
com, err := commit([]*math.Zr{
math.Curves[pp.Curve].HashToZr([]byte(meta.Type)),
meta.Value,
meta.BlindingFactor,
}, pp.PedersenGenerators, math.Curves[pp.Curve])
if err != nil {
return nil, errors.Wrap(err, "failed to check token data")
}
// Verify commitment matches
if !com.Equals(t.Data) {
return nil, ErrTokenMismatch
}
return &token.Token{
Type: meta.Type,
Quantity: "0x" + meta.Value.String(),
Owner: t.Owner,
}, nil
}
Implementation: crypto/upgrade/
Allows migrating cleartext FabToken outputs to privacy-preserving ZKAT-DLOG commitments.
sequenceDiagram
participant C as Client
participant US as UpgradeService
participant IS as IssueService
C->>US: ProcessTokensUpgradeRequest(fabtokens)
US->>US: Validate FabToken format
US->>US: Extract (type, value)
US->>US: Generate blinding factor
US-->>C: UpgradedTokens
C->>IS: Issue(nil, nil, nil, owners, opts)
Note over IS: opts.TokensUpgradeRequest = fabtokens
IS->>IS: Extract type and sum values
IS->>IS: Generate commitments
IS->>IS: Create IssueAction with inputs
IS-->>C: IssueAction + Metadata
When spending legacy tokens (e.g., FabToken) with the ZKAT-DLOG driver, an Upgrade Witness is automatically generated to prove the validity of the conversion. This enables seamless in-place upgrades without requiring explicit migration transactions.
Structure (defined in token/token.go):
The TransferActionInputUpgradeWitness protobuf message contains:
output (fabtoken.Token): The original legacy token in cleartextblinding_factor (Zr): Blinding factor generated for the Pedersen commitmentPurpose:
How It Works:
Compatibility Criteria:
SupportedTokenFormats() listFor more details on upgradability, see the Upgradability Guide.
For tokens that cannot be upgraded in-place (e.g., incompatible precision or major protocol changes), the driver supports an atomic “Burn and Re-issue” mechanism:
IssueAction.inputs)IssueAction.outputs)Implementation: See crypto/upgrade/ for the upgrade service implementation.
The driver provides comprehensive validation and auditing capabilities to ensure transaction integrity and regulatory compliance.
Implementation: validator/
The validator performs rigorous checks on token transactions before they are committed to the ledger.
The validation process consists of multiple stages:
Validation Flow:
graph TD
A[Token Request] --> B[Deserialize Actions]
B --> C{Action Type?}
C -->|Issue| D[Verify Issue Proof]
C -->|Transfer| E[Verify Transfer Proof]
D --> F[Check Issuer Authorization]
E --> G[Verify Owner Signatures]
F --> H[Validate Commitments]
G --> H
H --> I[Check Balance]
I --> J{Valid?}
J -->|Yes| K[Accept]
J -->|No| L[Reject]
The validator is instantiated with the public parameters and requires no additional configuration. It operates statelessly, relying only on the ledger state and the public parameters for validation decisions.
For implementation details, see validator/validator.go.
Implementation: audit/auditor.go and auditor.go
The auditor service enables authorized auditors to inspect private transactions and verify compliance without compromising the privacy of non-audited users.
Auditors receive cleartext metadata (via transient data) that includes:
The auditor verifies that:
InfoMatcherThe Auditor.Check() method performs comprehensive validation in two phases:
Phase 1: Structural Validation (validateStructure())
Phase 2: Semantic Validation (per action type)
For Issue Actions (checkIssueAction()):
IssueMetadata.Match() to validate structural correspondencevalidateIssueInputs():
validateIssueOutputs():
validateOutputReceivers()validateIssuer()For Transfer Actions (checkTransferAction()):
TransferMetadata.Match() to validate structural correspondencevalidateTransferInputs():
InfoMatchervalidateTransferOutputs():
Common Validation Methods:
validateOutputReceivers(): Extracts recipients from owner, validates count matches, verifies identity matching (with configurable empty identity handling)InspectOutput(): Recomputes and verifies Pedersen commitmentsInspectIdentity(): Verifies audit info matches identity via InfoMatcher.MatchIdentity()Privacy Guarantee: Only authorized auditors with the correct audit keys can decrypt the metadata. The ledger itself stores only commitments, preserving privacy for all other participants.
For implementation details, see audit/auditor.go.
IS-»IS: Create IssueAction with Inputs
IS–»C: (IssueAction, Metadata)
---
## 9. Validation and Auditing
### 9.1 Validator
**Implementation**: [`validator/validator.go`](/panurus/token/core/zkatdlog/nogh/v1/validator/validator.go)
Stateless verification of `TokenRequest`.
#### 9.1.1 Validation Pipeline
```mermaid
graph TB
subgraph "Validation Pipeline"
A[TokenRequest] --> B[Deserialize Actions]
B --> C{Action Type?}
C -->|Issue| D[Issue Validators]
C -->|Transfer| E[Transfer Validators]
D --> D1[Action Validate]
D --> D2[ZKP Verify]
D --> D3[Issuer Signature]
D --> D4[Application Data]
E --> E1[Action Validate]
E --> E2[Signature Validate]
E --> E3[Upgrade Witness]
E --> E4[ZKP Verify]
E --> E5[HTLC Validate]
E --> E6[Application Data]
D1 --> F[Auditing Validators]
D2 --> F
D3 --> F
D4 --> F
E1 --> F
E2 --> F
E3 --> F
E4 --> F
E5 --> F
E6 --> F
F --> F1[Auditor Signature]
F1 --> G{Valid?}
G -->|Yes| H[Accept]
G -->|No| I[Reject]
end
The protocol uses a two-layer encoding strategy:
// From protos-go/utils/utils.go
func ToProtoZr(zr *math.Zr) (*math2.Zr, error) {
if zr == nil {
return nil, errors.New("cannot convert nil Zr to proto")
}
return &math2.Zr{
Value: zr.Bytes(),
}, nil
}
func FromZrProto(zr *math2.Zr) (*math.Zr, error) {
if zr == nil {
return nil, errors.New("cannot convert nil proto to Zr")
}
return math.Curves[curveID].NewZrFromBytes(zr.Value), nil
}
func ToProtoG1(g1 *math.G1) (*math2.G1, error) {
if g1 == nil {
return nil, errors.New("cannot convert nil G1 to proto")
}
return &math2.G1{
Value: g1.Bytes(),
}, nil
}
func FromG1Proto(g1 *math2.G1) (*math.G1, error) {
if g1 == nil {
return nil, errors.New("cannot convert nil proto to G1")
}
return math.Curves[curveID].NewG1FromBytes(g1.Value), nil
}
message Token {
bytes owner = 1; // Serialized identity
G1 data = 2; // Pedersen commitment
}
All tokens are wrapped with a type identifier for proper deserialization:
// From token/services/tokens/core/comm/token.go
const Type = "zkatdlog"
func WrapTokenWithType(raw []byte) ([]byte, error) {
return proto.Marshal(&TypedToken{
Type: Type,
Token: raw,
})
}
func UnmarshalTypedToken(bytes []byte) (*TypedToken, error) {
typed := &TypedToken{}
if err := proto.Unmarshal(bytes, typed); err != nil {
return nil, err
}
return typed, nil
}
message TokenMetadata {
string type = 1; // Token type (e.g., "USD")
Zr value = 2; // Token value
Zr blinding_factor = 3; // Blinding factor r
Identity issuer = 4; // Issuer identity (for issue)
}
func (m *Metadata) Serialize() ([]byte, error) {
value, err := utils.ToProtoZr(m.Value)
if err != nil {
return nil, errors.Wrapf(err, "failed to serialize metadata")
}
blindingFactor, err := utils.ToProtoZr(m.BlindingFactor)
if err != nil {
return nil, errors.Wrapf(err, "failed to serialize metadata")
}
raw, err := proto.Marshal(&actions.TokenMetadata{
Type: string(m.Type),
Value: value,
BlindingFactor: blindingFactor,
Issuer: &pp.Identity{Raw: m.Issuer},
})
if err != nil {
return nil, errors.Wrapf(err, "failed serializing token")
}
return comm.WrapMetadataWithType(raw)
}
message IssueAction {
bytes issuer = 1; // X.509 identity
repeated Token outputs = 2; // Output commitments
IssueProof proof = 3; // ZKP
repeated ActionInput inputs = 4; // Optional: for upgrade
bytes metadata = 5; // Application data
}
message TransferAction {
repeated ActionInput inputs = 1; // Input tokens
repeated Token outputs = 2; // Output commitments
TransferProof proof = 3; // TypeAndSumProof + RangeProofs
bytes issuer = 4; // Optional: for redemption
bytes metadata = 5; // Application data
}
message ActionInput {
TokenID id = 1; // (TxID, Index)
Token token = 2; // Input commitment
UpgradeWitness upgrade_witness = 3; // Optional: for upgrade
}
Public parameters are serialized with a two-layer wrapper:
func (p *PublicParams) Serialize() ([]byte, error) {
// Serialize inner protobuf
publicParams := &pp.PublicParameters{
TokenDriverName: string(p.DriverName),
TokenDriverVersion: uint64(p.DriverVersion),
CurveId: &math2.CurveID{Id: uint64(p.Curve)},
PedersenGenerators: pg,
RangeProofParams: rpp,
IdemixIssuerPublicKeys: idemixIssuerPublicKeys,
Auditors: auditors,
Issuers: issuers,
MaxToken: p.MaxToken,
QuantityPrecision: p.QuantityPrecision,
ExtraData: p.ExtraData,
}
raw, err := proto.Marshal(publicParams)
if err != nil {
return nil, err
}
// Wrap with driver identifier
return pp3.Marshal(&pp2.PublicParameters{
Identifier: string(core.DriverIdentifier(p.DriverName, p.DriverVersion)),
Raw: raw,
})
}
The ZKAT-DLOG (NOGH) driver uses Protocol Buffers for all serialized data structures, ensuring consistent communication between nodes and the ledger while guaranteeing backward and forward compatibility. The definitions are located in token/core/zkatdlog/nogh/protos/.
noghactions.proto)Token: Represents a ZKAT-DLOG token as a Pedersen commitment.
owner (bytes): Serialized identity of the owner (Idemix pseudonym for end-users, X.509 for issuers/auditors)data (G1): Elliptic curve point representing the Pedersen commitment to token type, value, and blinding factorTokenMetadata: Cleartext token information (sent via transient data, not stored on ledger).
type (string): Token type (e.g., “USD”, “EUR”)value (Zr): Token quantity as a scalar field elementblinding_factor (Zr): Random blinding factor used in the commitmentissuer (Identity): Identity of the token issuerIssueAction: Creates new tokens on the ledger.
version (uint64): Protocol versionissuer (Identity): X.509 identity of the authorizing issuerinputs (repeated IssueActionInput): Optional tokens being redeemed (for upgrade scenarios)outputs (repeated IssueActionOutput): New token commitments being createdproof (Proof): Zero-knowledge proof of correct issuancemetadata (map<string, bytes>): Application-level metadataTransferAction: Transfers token ownership.
version (uint64): Protocol versioninputs (repeated TransferActionInput): Tokens being spent (includes token ID, commitment, and optional upgrade witness)outputs (repeated TransferActionOutput): New token commitments being createdproof (Proof): Zero-knowledge proof of balance preservation and ownershipissuer (Identity): Optional issuer identity (required for redemptions)metadata (map<string, bytes>): Application-level metadataTransferActionInputUpgradeWitness: Enables in-place upgrades from legacy token formats.
output (fabtoken.Token): The original legacy token (e.g., FabToken)blinding_factor (Zr): Blinding factor generated for the commitmentnoghpp.proto)PublicParameters: Defines the cryptographic setup and governance rules.
token_driver_name (string): Always "zkatdlognogh"token_driver_version (uint64): Protocol version (currently 1)curve_id (CurveID): Pairing-friendly elliptic curve identifier (e.g., BLS12-381, BN254)pedersen_generators (repeated G1): Generators for Pedersen commitments (g₀, g₁, g₂)range_proof_params (RangeProofParams): Bulletproof parameters for range proofsidemix_issuer_public_keys (repeated IdemixIssuerPublicKey): Public keys for Idemix credential verificationauditors (repeated Identity): List of authorized auditor identities (X.509)issuers (repeated Identity): List of authorized issuer identities (X.509)max_token (uint64): Maximum token value (2^precision - 1)quantity_precision (uint64): Bit-precision for token quantities (16, 32, or 64)extra_data (map<string, bytes>): Extensibility map for custom parametersRangeProofParams: Bulletproof configuration for proving values are in valid range.
left_generators (repeated G1): Left-side generators for inner product argumentright_generators (repeated G1): Right-side generators for inner product argumentP (G1): Generator P for the inner productQ (G1): Generator Q for the inner productbit_length (uint64): Number of bits in the range (e.g., 64)number_of_rounds (uint64): Number of rounds in the protocol (log₂ of bit_length)noghmath.proto)G1: Elliptic curve point in group G₁.
raw (bytes): Compressed point serialization (48 bytes for BLS12-381, 32 bytes for BN254)Zr: Scalar field element.
raw (bytes): Big-endian byte representation of the scalarCurveID: Identifier for the elliptic curve.
id (uint64): Numeric curve identifier from the mathlib librarySerialization Notes:
The driver requires persistent storage for various components, managed by the Storage Service.
graph TB
subgraph "Storage Layer"
V[Vault]
W[Wallet Storage]
I[Identity Storage]
end
subgraph "Vault Storage"
V --> T[Tokens DB]
V --> M[Metadata DB]
V --> C[Certification DB]
end
subgraph "Wallet Storage"
W --> O[Owner Wallets]
W --> IS[Issuer Wallets]
W --> A[Auditor Wallets]
end
subgraph "Identity Storage"
I --> ID[Idemix Credentials]
I --> X[X.509 Certificates]
I --> N[Nym Mappings]
end
Stores token commitments and their metadata.
Schema:
Key: (TxID, Index)
Value: {
Token: <serialized Token>,
Metadata: <serialized Metadata>,
Format: "zkatdlog" | "fabtoken",
Owner: <identity>,
Type: <token type>,
Quantity: <hex string>,
Status: "unspent" | "spent" | "pending"
}
Indexes:
owner -> [(TxID, Index)]type -> [(TxID, Index)]status -> [(TxID, Index)]The vault provides a query engine for efficient token retrieval:
type QueryEngine interface {
// GetToken retrieves a token by its ID
GetToken(ctx context.Context, id *token.ID) (*Token, error)
// GetTokens retrieves multiple tokens by their IDs
GetTokens(ctx context.Context, ids []*token.ID) ([]*Token, error)
// ListUnspentTokens returns all unspent tokens for an owner
ListUnspentTokens(ctx context.Context, owner driver.Identity) ([]*Token, error)
// ListUnspentTokensByType returns unspent tokens of a specific type
ListUnspentTokensByType(ctx context.Context, owner driver.Identity, tokenType string) ([]*Token, error)
}
Stores Idemix credentials and pseudonyms.
Schema:
Key: WalletID
Value: {
EnrollmentID: <enrollment identity>,
Credential: <Idemix credential (A, e, v)>,
SecretKey: <sk>,
Pseudonyms: [
{
Nym: <N = g^sk · h^r>,
Randomness: <r>,
EnrollmentIDCommitment: <Nym EID>
}
]
}
Stores X.509 certificates and signing keys.
Schema:
Key: IssuerID
Value: {
Certificate: <X.509 cert>,
PrivateKey: <signing key>,
TokenTypes: [<authorized types>]
}
Stores X.509 certificates and audit keys.
Schema:
Key: AuditorID
Value: {
Certificate: <X.509 cert>,
PrivateKey: <signing key>,
AuditKey: <decryption key for audit info>
}
Managed by the Identity Service.
Schema:
Key: EnrollmentID
Value: {
IssuerPublicKey: <IPK>,
Credential: {
A: <G1 element>,
e: <Zr element>,
v: <Zr element>
},
Attributes: {
EnrollmentID: <EID>,
Role: <role>,
...
}
}
Maintains the mapping between enrollment IDs and pseudonyms for identity resolution.
Schema:
Key: NymEID (Nym Enrollment ID Commitment)
Value: {
EnrollmentID: <EID>,
Nym: <N = g^sk · h^r>,
Randomness: <r>
}
Stores certification information for tokens (if certification is enabled).
Note: The ZKAT-DLOG (NOGH) driver does not require a certification service because the spending graph is visible (input token IDs are revealed). This transparency allows validators to directly verify token lineage and detect double-spending without additional certification proofs.
Certification is primarily useful for drivers with graph hiding properties where the spending graph must be proven valid through additional cryptographic attestations. Since NOGH explicitly reveals the graph for efficiency, certification becomes redundant.
If certification is enabled at the TMS level (via the common Certifier Service), the storage schema above will be used, but it is not a core requirement for this driver’s operation.
Schema:
Key: (TxID, Index)
Value: {
Certifier: <certifier identity>,
Signature: <certification signature>,
Timestamp: <certification time>
}
For a typical deployment with 1M tokens:
| Component | Size per Entry | Total (1M tokens) |
|---|---|---|
| Token Commitment | ~100 bytes | ~100 MB |
| Metadata | ~150 bytes | ~150 MB |
| Indexes | ~50 bytes | ~50 MB |
| Wallet Data | ~5 KB per wallet | ~5 MB (1K wallets) |
| Total | ~305 MB |
Inflation Protection: The binding property of Pedersen commitments and the soundness of the TypeAndSumProof ensure that:
No party can create value out of thin air.
Range Validity: Bulletproofs ensure that all output values are in the valid range $[0, 2^{64}-1]$, preventing:
Value Privacy: Observers learn nothing about $T$ or $V$ from the on-ledger data. The Pedersen commitment is computationally hiding under the discrete logarithm assumption.
Type Privacy: Token types are hidden through the hash function $H_{Type}$, which maps types to random-looking field elements.
Idemix Properties:
Graph Transparency: Input token IDs $(TxID, Index)$ are revealed, allowing the ledger to:
Selective Auditability: Only authorized auditors can:
Audit Trail: All audit actions are logged and signed, providing:
The security of ZKAT-DLOG (NOGH) relies on:
Quantum Resistance: The current implementation is not quantum-resistant. Post-quantum variants would require:
Side-Channel Resistance: The implementation includes protections against:
Validation Errors:
var (
ErrInvalidProof = errors.New("invalid zero-knowledge proof")
ErrInvalidCommitment = errors.New("invalid Pedersen commitment")
ErrTokenMismatch = errors.New("token does not match metadata")
ErrInvalidSignature = errors.New("invalid signature")
ErrUnauthorizedIssuer = errors.New("unauthorized issuer")
ErrUnauthorizedAuditor = errors.New("unauthorized auditor")
)
Cryptographic Errors:
var (
ErrInvalidCurve = errors.New("invalid or unsupported curve")
ErrInvalidFieldElement = errors.New("invalid field element")
ErrInvalidGroupElement = errors.New("invalid group element")
ErrProofGeneration = errors.New("proof generation failed")
)
Graceful Degradation:
Logging and Monitoring:
Located in *_test.go files throughout the codebase:
crypto/rp/bulletproof/rp_test.goissue_test.go, transfer_test.govalidator/validator_test.goLocated in integration/token/:
Located in regression/:
For detailed benchmark results and analysis, see the ZKAT-DLOG Benchmark Documentation.
The driver currently implements 2 core performance metrics:
Performance Metrics:
// From metrics.go
type Metrics struct {
zkIssueDuration metrics.Histogram // Duration of ZK issue operations
zkTransferDuration metrics.Histogram // Duration of ZK transfer operations
}
Metric Details:
issue_duration: Histogram tracking time to generate ZK issue proofs
network, channel, namespacetransfer_duration: Histogram tracking time to generate ZK transfer proofs
network, channel, namespaceUsage: Metrics are automatically collected during issue and transfer operations (see issue.go:120-126 and transfer.go:165-173).
Note: Additional business metrics (issuance rate, transfer volume, audit frequency, error rates) can be added by extending the Metrics struct and instrumenting the relevant service methods.
Tracing: OpenTelemetry integration for distributed tracing Logging: Structured logging with context Alerting: Prometheus-compatible metrics
Core Implementation:
driver/driver.goservice.goissue.gotransfer.goauditor.gosetup/setup.gotoken/service.goCryptographic Primitives:
issue/transfer/audit/validator/crypto/upgrade/Testing:
issue_test.go, transfer_test.gobenchmark/integration/token/fungible/dlog/, integration/token/nft/dlog/For Production Use:
For Development/Testing:
From FabToken to ZKAT-DLOG:
Version Upgrades:
Common Issues:
Debug Tools:
This specification document is maintained by Panurus team. For questions or contributions, please refer to the CONTRIBUTING.md guidelines.