Skip to content

Commit d3c4f5b

Browse files
authored
Merge branch 'main' into Soumya8898/extend-issuer-wallet
2 parents c87da23 + 814dad0 commit d3c4f5b

110 files changed

Lines changed: 3945 additions & 344 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,3 +231,11 @@ install-linter-tool:
231231
fmt: ## Run gofmt on the entire project
232232
@echo "Running gofmt..."
233233
@gofmt -l -s -w .
234+
235+
.PHONY: update-all-deps-latest
236+
update-all-deps-latest: ## Update all dependencies in all Go modules to their latest version
237+
@echo "Updating all dependencies to @latest..."
238+
@for dir in $$(find . -name "go.mod" -exec dirname {} \;); do \
239+
echo "=> Updating dependencies in $$dir"; \
240+
(cd $$dir && go get ./...@latest && go mod tidy); \
241+
done

cmd/tokengen/testdata/zkatdlognoghv1_pp.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

docs/services/identity.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,24 @@ Located in `token/services/identity/multisig`.
232232
* **Usage**: Useful for requiring multiple signatures or representing a group of parties.
233233
* **Auditability**: Aggregates audit information for all underlying identities.
234234

235+
#### PolicyIdentity (Boolean-Expression-Governed Ownership)
236+
Located in `token/services/identity/boolpolicy`.
237+
* **Concept**: An identity whose ownership is governed by a boolean expression over a set of component identities, enabling OR-style (any one signer suffices) and AND-style (all signers required) multi-party control without a fixed M-of-N scheme.
238+
* **Policy Expression Syntax**: A string using `$N` slot references and the operators `AND`, `OR`, and parentheses:
239+
- `$0 OR $1` — either component identity 0 or 1 can satisfy ownership alone.
240+
- `$0 AND $1` — both component identity 0 and 1 must sign.
241+
- `($0 OR $1) AND $2` — one of the first two parties plus the third must sign.
242+
* **Identity (Payload)**: An ASN.1-encoded `PolicyIdentity` sequence:
243+
- `policy` (UTF8String): the boolean expression, e.g. `"$0 OR $1"`.
244+
- `identities` (SEQUENCE OF OCTET STRING): ordered list of raw component identity bytes; `$N` indexes into this list.
245+
* **Audit Info**: JSON-encoded `AuditInfo` structure.
246+
- `IdentityAuditInfos` (array of `IdentityAuditInfo`): per-component audit info blobs in the same order as `identities`.
247+
* **Encoding**:
248+
- `TypedIdentity` payload: ASN.1 DER.
249+
- Audit Info: JSON.
250+
* **Signature Representation**: An ASN.1 `PolicySignature` (`SEQUENCE OF OCTET STRING`) where each slot corresponds to one component identity. A slot may be nil/empty when that component does not need to sign (valid for OR branches).
251+
* **Implementation**: `token/services/identity/boolpolicy`.
252+
235253
#### HTLC (Hashed Time Lock Contract)
236254
Located in `token/services/identity/interop/htlc`.
237255
* **Concept**: A script-based identity used primarily for interoperability mechanisms like atomic swaps.
@@ -258,3 +276,111 @@ Typical extension scenarios include:
258276
- Supporting a new identity type by implementing a custom `KeyManager`
259277
- Customizing signature generation or verification logic within a `KeyManager`
260278
- Providing a custom `KeyManagerProvider` to plug new identity mechanisms into `LocalMembership`
279+
280+
### Step-by-Step Guide: Introducing a New Identity Type
281+
282+
The steps below describe how to add a new composite identity type end-to-end, based on the pattern used for **PolicyIdentity** (`token/services/identity/boolpolicy`).
283+
284+
#### Step 1 — Reserve a type tag
285+
286+
Add a new constant to `token/driver/wallet.go` alongside the existing tags:
287+
288+
```go
289+
const (
290+
// ...existing tags...
291+
MyNewIdentityType IdentityType = 7
292+
MyNewIdentityTypeString = "mynew"
293+
)
294+
```
295+
296+
The integer must be unique across all registered identity types.
297+
298+
#### Step 2 — Define the wire format
299+
300+
Create a package (e.g. `token/services/identity/mynew/`) and define the identity struct. Use ASN.1 DER for structured binary data (as PolicyIdentity does) or JSON for human-readable payloads (as HTLC does):
301+
302+
```go
303+
type MyNewIdentity struct {
304+
SomeField string `asn1:"utf8"`
305+
Parts [][]byte
306+
}
307+
308+
func (m *MyNewIdentity) Serialize() ([]byte, error) { return asn1.Marshal(*m) }
309+
func (m *MyNewIdentity) Deserialize(raw []byte) error {
310+
_, err := asn1.Unmarshal(raw, m)
311+
return err
312+
}
313+
```
314+
315+
Expose `Wrap` / `Unwrap` helpers (see `boolpolicy.WrapPolicyIdentity` / `boolpolicy.Unwrap`) that embed the serialized struct inside a `TypedIdentity` envelope with the new type tag.
316+
317+
#### Step 3 — Implement signature verification
318+
319+
Add a `Verifier` that accepts the new signature format and a `Deserializer` that reconstructs a `Verifier` from raw identity bytes. Register the deserializer via `des.AddTypedVerifierDeserializer(mynew.MyNewIdentityType, ...)` in each driver's `NewTokenService` (see `token/core/fabtoken/v1/driver/driver.go` and the zkatdlog equivalent).
320+
321+
#### Step 4 — Define the signature format
322+
323+
Define a struct for the signature produced over the token request (analogous to `PolicySignature` in `boolpolicy/sig.go`). Include ASN.1 or JSON encoding helpers and a `JoinSignatures` function if multiple parties contribute partial signatures.
324+
325+
#### Step 5 — Implement the `Authorization` checker
326+
327+
Create an `EscrowAuth` struct (see `token/services/ttx/boolpolicy/auth.go`) that implements the `Authorization` interface:
328+
329+
```go
330+
type EscrowAuth struct{ WalletService driver.WalletService }
331+
func (a *EscrowAuth) AmIAnAuditor() bool { return false }
332+
func (a *EscrowAuth) IsMine(ctx context.Context, tok *token.Token) (string, []string, bool) { ... }
333+
func (a *EscrowAuth) Issued(_ context.Context, _ driver.Identity, _ *token.Token) bool { return false }
334+
func (a *EscrowAuth) OwnerType(raw []byte) (driver.IdentityType, []byte, error) { ... }
335+
```
336+
337+
Register it in **both** driver files inside `NewAuthorizationMultiplexer`:
338+
339+
```go
340+
// token/core/fabtoken/v1/driver/driver.go (and the zkatdlog equivalent)
341+
authorization := common.NewAuthorizationMultiplexer(
342+
common.NewTMSAuthorization(...),
343+
htlc.NewScriptAuth(ws),
344+
multisig.NewEscrowAuth(ws),
345+
boolpolicy.NewEscrowAuth(ws),
346+
mynew.NewEscrowAuth(ws), // ← add here
347+
)
348+
```
349+
350+
#### Step 6 — Add a wallet wrapper
351+
352+
Create an `OwnerWallet` wrapper (see `token/services/ttx/boolpolicy/wallet.go`) that filters the unspent token list to tokens whose owner is the new identity type, and exposes domain-specific helpers (e.g. `VerifyApprover`).
353+
354+
#### Step 7 — Wire the recipient-negotiation protocol
355+
356+
If the new identity requires interactive negotiation between parties to assemble the composite identity before a transfer, add a `RequestMyNewIdentity` function following the pattern of `ttx.RequestPolicyIdentity` (`token/services/ttx/recipients.go`). The function sends a typed request, each counterparty responds with its component data, and the initiator assembles the final composite identity.
357+
358+
#### Step 8 — Add integration views
359+
360+
Create initiator and responder views in the integration layer (e.g. `integration/token/fungible/views/mynew.go`) following the pattern in `boolpolicy.go`:
361+
362+
- **Lock view** — transfers tokens to a recipient with the new composite identity.
363+
- **Spend view** — spends those tokens, optionally with restricted signer sets.
364+
- **Balance view** — queries the policy-owned token balance (modelled on `PolicyOwnedBalanceView`).
365+
- **Responder views** — ACK and endorse spend requests for AND-style policies.
366+
367+
Register all view factories and responders in the integration SDK (`integration/token/fungible/sdk/party/sdk.go`).
368+
369+
#### Step 9 — Add tests
370+
371+
- **Unit tests** for the verifier (`sig_test.go` pattern) and for `EscrowAuth.IsMine` (`auth_test.go` pattern).
372+
- **Integration tests** in `integration/token/fungible/tests.go` + the relevant `dlog_test.go` `Describe` block, following `TestPolicyOR` / `TestPolicyAND`.
373+
374+
#### Summary checklist
375+
376+
| # | What | Where |
377+
|:--|:-----|:------|
378+
| 1 | Reserve type tag | `token/driver/wallet.go` |
379+
| 2 | Wire format + Wrap/Unwrap | `token/services/identity/mynew/` |
380+
| 3 | Verifier + Deserializer | same package; register in both drivers |
381+
| 4 | Signature format + JoinSignatures | same package |
382+
| 5 | EscrowAuth + register in drivers | `token/services/ttx/mynew/auth.go` |
383+
| 6 | OwnerWallet wrapper | `token/services/ttx/mynew/wallet.go` |
384+
| 7 | Recipient-negotiation protocol | `token/services/ttx/recipients.go` |
385+
| 8 | Integration views + SDK registration | `integration/token/fungible/views/mynew.go` |
386+
| 9 | Unit + integration tests | alongside each new file |

docs/services/ttx.md

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,61 @@ sequenceDiagram
159159
Note over I,R: Full RecipientData on wire today (responder sends local wallet RecipientData)
160160
```
161161

162+
### PolicyIdentity — Boolean-Expression-Governed Ownership
163+
164+
The TTX service supports **PolicyIdentity** owners: tokens whose spending requires satisfying a boolean expression over a set of component identities. This enables richer access-control than simple multisig (M-of-N) — for example, an OR clause where any single co-owner may spend unilaterally, or complex nested expressions.
165+
166+
#### Creating a PolicyIdentity
167+
168+
Call `RequestPolicyIdentity` (in `token/services/ttx/recipients.go`) to negotiate a composite identity from all co-owners before building the transfer:
169+
170+
```go
171+
recipient, err := bptx.RequestRecipientIdentity(ctx, "$0 OR $1",
172+
[]view.Identity{bobFSCIdentity, charlieFSCIdentity},
173+
token.WithTMSIDPointer(tmsID),
174+
)
175+
```
176+
177+
Each co-owner's node responds with its component identity; the SDK assembles the `PolicyIdentity` envelope automatically.
178+
179+
#### Policy Expression Syntax
180+
181+
| Expression | Meaning |
182+
|:-----------|:--------|
183+
| `$0 OR $1` | Either component 0 **or** component 1 can spend alone. |
184+
| `$0 AND $1` | Both component 0 **and** component 1 must sign. |
185+
| `($0 OR $1) AND $2` | One of the first two parties plus party 2 must sign. |
186+
187+
`$N` is a zero-based index into the ordered component identity list supplied when creating the token.
188+
189+
#### Spending — OR Policy
190+
191+
For an OR policy the initiator alone can satisfy the policy. Pass `WithPolicySigners` to restrict signature collection to only the signing party's slot; the remaining slots are left nil (which is valid for OR branches):
192+
193+
```go
194+
_, err = context.RunView(ttx.NewCollectEndorsementsView(tx,
195+
ttx.WithPolicySigners(myComponentIdentity),
196+
))
197+
```
198+
199+
#### Spending — AND Policy
200+
201+
For an AND policy all co-owners must endorse. Use `RequestSpendView` (in `token/services/ttx/boolpolicy/spend.go`) to notify co-owners before assembling the transaction, then collect endorsements from all components without restriction:
202+
203+
```go
204+
_, err = context.RunView(bptx.NewRequestSpendView(unspentToken, serviceOpts...))
205+
// ... build tx ...
206+
_, err = context.RunView(ttx.NewCollectEndorsementsView(tx))
207+
```
208+
209+
Co-owners run `EndorseSpendView` (via `EndorseSpend`) on their side, which ACKs the spend request and then endorses the assembled transaction.
210+
211+
#### Wallet and Authorization
212+
213+
The `boolpolicy.OwnerWallet` (in `token/services/ttx/boolpolicy/wallet.go`) wraps a standard owner wallet and filters the token list to policy-type tokens. `VerifyApprover` can be used to assert that a given identity is one of the named component identities before allowing a spend.
214+
215+
The `EscrowAuth` struct (in `token/services/ttx/boolpolicy/auth.go`) implements the `Authorization` interface: `IsMine` returns true if any component identity of the policy token belongs to one of the node's owner wallets.
216+
162217
## Token Operations
163218

164219
The TTX service supports three primary operations through the `TokenRequest` API:
@@ -178,11 +233,17 @@ Enables the transfer of token ownership. The service:
178233
### Redeem
179234
A specialized transfer where the recipient is "hidden" or "empty," effectively removing the tokens from circulation on the ledger.
180235

236+
Redeem supports an enhanced flow where an issuer signature is required as part of transfer validation:
237+
1. Add the redeem action with `tx.Redeem(...)`.
238+
2. If the issuer endpoint cannot be resolved automatically, pass `ttx.WithFSCIssuerIdentity(...)` so the initiator can contact the issuer for endorsement.
239+
3. Optionally pass `ttx.WithIssuerPublicParamsPublicKey(...)` to pin which issuer public-parameters signing key must authorize the redeem.
240+
4. Run `CollectEndorsementsView` to collect owner, auditor (if configured), and issuer signatures.
241+
181242
## Collecting Endorsements
182243

183244
The `CollectEndorsementsView` is responsible for gathering all signatures required to make a transaction valid:
184245
* **Owner Signatures**: For every token spent, the service requests a signature from the node that owns the corresponding identity.
185-
* **Issuer Signatures**: For transactions involving token issuance.
246+
* **Issuer Signatures**: For transactions involving token issuance and enhanced redeem flows that require issuer authorization.
186247
* **Auditor Signatures**: If the TMS is configured with an auditor, the transaction must be approved via the `AuditApproveView`.
187248
* **Network Endorsements**: The service delegates to the **Network Service** to obtain backend-specific endorsements (e.g., Fabric chaincode endorsements).
188249

docs/token_sdk_usage.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,13 +143,15 @@ if err != nil {
143143
}
144144

145145
// 2. Add Redeem Action
146-
// If the issuer is not automatically resolvable, provide their identity.
146+
// If the issuer is not automatically resolvable, provide their FSC identity.
147+
// If needed, also pin the issuer signing key expected by public parameters.
147148
senderWallet := ttx.GetWallet(context, senderWalletID)
148149
err = tx.Redeem(
149150
senderWallet,
150151
tokenType,
151152
amount,
152-
ttx.WithFSCIssuerIdentity(issuerIdentity), // Contact issuer for approval
153+
ttx.WithFSCIssuerIdentity(issuerIdentity), // Contact issuer for approval
154+
ttx.WithIssuerPublicParamsPublicKey(issuerPublicParamsPubKey), // Optional key pinning
153155
)
154156
if err != nil {
155157
return nil, err
@@ -168,6 +170,9 @@ if err != nil {
168170
}
169171
```
170172

173+
Use `ttx.WithFSCIssuerIdentity(...)` when your app cannot resolve the issuer endpoint automatically.
174+
Use `ttx.WithIssuerPublicParamsPublicKey(...)` when you want redeem authorization to be verified against a specific issuer key from public parameters.
175+
171176
---
172177

173178
## 5. Atomic Swap ([`swap.go`](../integration/token/fungible/views/swap.go))

docs/tokenapi.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ A [Request](../token/request.go) is a ledger-agnostic blueprint for a token tran
5959

6060
### Core Actions
6161
* **Issue**: Minting new tokens into the system.
62-
* **Transfer**: Reassigning ownership of existing tokens (includes **Redeem** by transferring to a null owner).
62+
* **Transfer**: Reassigning ownership of existing tokens (includes **Redeem** by transferring to a null owner; enhanced redeem flows can additionally require issuer authorization/signature).
6363

6464
### The Request Lifecycle
6565
1. **Assemble**: Add actions to the `Request` using a TMS.

go.mod

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ require (
1010
github.com/consensys/gnark-crypto v0.20.1
1111
github.com/dgraph-io/ristretto/v2 v2.4.0
1212
github.com/gin-gonic/gin v1.12.0
13-
github.com/go-co-op/gocron/v2 v2.19.1
13+
github.com/go-co-op/gocron/v2 v2.21.1
1414
github.com/google/pprof v0.0.0-20260402051712-545e8a4df936
1515
github.com/hashicorp/go-uuid v1.0.3
1616
github.com/hyperledger-labs/fabric-smart-client v0.10.2-0.20260428094934-a70a13e26c74
@@ -30,17 +30,16 @@ require (
3030
github.com/spf13/viper v1.21.0
3131
github.com/stretchr/testify v1.11.1
3232
github.com/tedsuo/ifrit v0.0.0-20230516164442-7862c310ad26
33-
github.com/test-go/testify v1.1.4
3433
github.com/thedevsaddam/gojsonq v2.3.0+incompatible
3534
go.opentelemetry.io/otel/trace v1.43.0
3635
go.uber.org/dig v1.19.0
3736
go.uber.org/zap v1.27.1
38-
golang.org/x/crypto v0.49.0
39-
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90
37+
golang.org/x/crypto v0.50.0
38+
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f
4039
golang.org/x/sync v0.20.0
4140
google.golang.org/protobuf v1.36.11
4241
gopkg.in/yaml.v2 v2.4.0
43-
modernc.org/sqlite v1.48.0
42+
modernc.org/sqlite v1.49.1
4443
)
4544

4645
require (
@@ -284,14 +283,14 @@ require (
284283
go.yaml.in/yaml/v2 v2.4.4 // indirect
285284
go.yaml.in/yaml/v3 v3.0.4 // indirect
286285
golang.org/x/arch v0.22.0 // indirect
287-
golang.org/x/mod v0.34.0 // indirect
288-
golang.org/x/net v0.52.0 // indirect
286+
golang.org/x/mod v0.35.0 // indirect
287+
golang.org/x/net v0.53.0 // indirect
289288
golang.org/x/oauth2 v0.35.0 // indirect
290-
golang.org/x/sys v0.42.0 // indirect
291-
golang.org/x/telemetry v0.0.0-20260311193753-579e4da9a98c // indirect
292-
golang.org/x/text v0.35.0 // indirect
289+
golang.org/x/sys v0.43.0 // indirect
290+
golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa // indirect
291+
golang.org/x/text v0.36.0 // indirect
293292
golang.org/x/time v0.14.0 // indirect
294-
golang.org/x/tools v0.43.0 // indirect
293+
golang.org/x/tools v0.44.0 // indirect
295294
gonum.org/v1/gonum v0.17.0 // indirect
296295
google.golang.org/api v0.215.0 // indirect
297296
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect
@@ -300,7 +299,7 @@ require (
300299
google.golang.org/grpc v1.79.3 // indirect
301300
gopkg.in/yaml.v3 v3.0.1 // indirect
302301
lukechampine.com/blake3 v1.4.1 // indirect
303-
modernc.org/libc v1.70.0 // indirect
302+
modernc.org/libc v1.72.0 // indirect
304303
modernc.org/mathutil v1.7.1 // indirect
305304
modernc.org/memory v1.11.0 // indirect
306305
)

0 commit comments

Comments
 (0)