Skip to content

Conversation

@wolf31o2
Copy link
Member

@wolf31o2 wolf31o2 commented Dec 6, 2025


Summary by cubic

Adds CIP-1854 multi-signature support with native scripts, key derivation, hashing, and address generation. Adds CLI and API endpoints to create, validate, and derive addresses for scripts.

  • New Features
    • Native script types: sig, all/any, n-of, before/after with CBOR encoding.
    • CIP-1854 account/payment/stake key derivation plus vkey/skey helpers.
    • Script hash (Blake2b-224) and address generation for specified network.
    • JSON marshal/unmarshal for scripts with hash and optional address.
    • CLI: bursa script create, validate, address with all/any/n-of and timelocks.
    • API: POST /api/script/create, /api/script/validate, /api/script/address with OpenAPI updates.
    • Expanded wallet model and OpenAPI docs with committee/DRep keys and stricter validation.

Written for commit 37830d9. Summary will update automatically on new commits.

Summary by CodeRabbit

  • New Features
    • Native multi-signature scripts (pubkey, all/any/n-of), timelocks, script hashing and address derivation; JSON marshal/unmarshal for scripts.
  • CLI
    • New script subcommands: create, validate, address (JSON I/O).
  • API
    • New REST endpoints to create, validate, and obtain script addresses with request/response models.
  • Tests
    • Extensive tests for script types, hashing, addresses, key derivation, validation, multisig generation and edge cases.
  • Docs
    • OpenAPI, generated client models and docs updated; wallet/key-file schemas and docs expanded.

✏️ Tip: You can customize this high-level summary in your review settings.

@wolf31o2 wolf31o2 requested review from a team as code owners December 6, 2025 06:13
@coderabbitai
Copy link

coderabbitai bot commented Dec 6, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Adds a native-script and multisig subsystem: new Script and NativeScript type aliases, constructors for ScriptSig/All/Any/NofK and timelocks, CBOR JSON marshal/unmarshal helpers, Blake2b-based script hashing and address derivation, and script validation with per-type validators and timelock checks. Exposes script operations via CLI commands (create/validate/address) and HTTP endpoints (/api/script/create, /api/script/validate, /api/script/address) with OpenAPI models, generated client code, and docs. Adds CIP‑1854 multisig key-derivation helpers, KeyFile helpers, extensive tests, and many autogenerated OpenAPI model/doc updates.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

  • Areas needing extra attention:
    • bursa.go: new Script aliases, CBOR/JSON (un)marshallers, Blake2b hashing, GetScriptHash/GetScriptAddress.
    • Script validation semantics: ValidateScript and per-type validators (n-of logic, signature handling, timelock boundaries).
    • CIP‑1854 key-derivation and KeyFile creation helpers.
    • internal/api/api.go + CLI handlers: input decoding/validation, hex handling, error responses.
    • Large autogenerated OpenAPI changes and docs: verify ABI/surface alignment and no unintended exports.
    • New tests: correctness and brittle assumptions.

Possibly related PRs

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: multi-sig script (CIP-1854)' accurately reflects the main objective of the pull request, which is to add multi-signature script support following the CIP-1854 standard.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/cip-1854-multisig-support

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 12 files

Prompt for AI agents (all 4 issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="openapi.sh">

<violation number="1" location="openapi.sh:5">
P2: The `sed -i` command is not portable across macOS and Linux. On macOS (BSD sed), `-i` requires a backup suffix argument. Use `sed -i &#39;&#39;` for in-place editing without backup on macOS, or use a portable alternative.</violation>
</file>

<file name="bursa.go">

<violation number="1" location="bursa.go:1082">
P0: This function always returns `true`, completely bypassing signature validation. This is a critical security issue - any script validation will pass regardless of whether valid signatures are provided. If this is intentional placeholder code, it should not be merged to production.</violation>
</file>

<file name="internal/api/api.go">

<violation number="1" location="internal/api/api.go:1151">
P1: Direct interpolation of `err.Error()` into JSON string can cause JSON injection or malformed responses if the error contains quotes or backslashes. Either use `json.Marshal` for proper escaping, or return a generic error message like other handlers do for internal errors.</violation>
</file>

<file name="internal/cli/cli.go">

<violation number="1" location="internal/cli/cli.go:184">
P2: If both `timelockBefore` and `timelockAfter` are specified, `timelockAfter` is silently ignored. Consider either validating they aren&#39;t both set, or applying both timelocks to the script.</violation>
</file>

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

openapi.sh Outdated
docker run --rm -v "${PWD}:/local" openapitools/openapi-generator-cli generate -i /local/docs/swagger.yaml --git-user-id blinklabs-io --git-repo-id bursa -g go -o /local/openapi -c /local/openapi-config.yml
make format golines
cd openapi && go mod tidy
cd openapi && sed -i 's/go 1.23/go 1.24.0/' go.mod && go mod tidy
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The sed -i command is not portable across macOS and Linux. On macOS (BSD sed), -i requires a backup suffix argument. Use sed -i '' for in-place editing without backup on macOS, or use a portable alternative.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At openapi.sh, line 5:

<comment>The `sed -i` command is not portable across macOS and Linux. On macOS (BSD sed), `-i` requires a backup suffix argument. Use `sed -i &#39;&#39;` for in-place editing without backup on macOS, or use a portable alternative.</comment>

<file context>
@@ -2,4 +2,4 @@
 docker run --rm -v &quot;${PWD}:/local&quot; openapitools/openapi-generator-cli generate -i /local/docs/swagger.yaml --git-user-id blinklabs-io --git-repo-id bursa -g go -o /local/openapi -c /local/openapi-config.yml
 make format golines
-cd openapi &amp;&amp; go mod tidy
+cd openapi &amp;&amp; sed -i &#39;s/go 1.23/go 1.24.0/&#39; go.mod &amp;&amp; go mod tidy
</file context>
Suggested change
cd openapi && sed -i 's/go 1.23/go 1.24.0/' go.mod && go mod tidy
cd openapi && sed -i.bak 's/go 1.23/go 1.24.0/' go.mod && rm -f go.mod.bak && go mod tidy
Fix with Cubic

bursa.go Outdated
func validateScriptSig(script *ScriptSig, signatures [][]byte) bool {
// Placeholder: in a real implementation, this would verify signatures against the key hash
// For testing script logic, we assume the script is satisfied if signatures are provided
return true // Always return true for now to test script structure logic
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: This function always returns true, completely bypassing signature validation. This is a critical security issue - any script validation will pass regardless of whether valid signatures are provided. If this is intentional placeholder code, it should not be merged to production.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At bursa.go, line 1082:

<comment>This function always returns `true`, completely bypassing signature validation. This is a critical security issue - any script validation will pass regardless of whether valid signatures are provided. If this is intentional placeholder code, it should not be merged to production.</comment>

<file context>
@@ -724,6 +818,323 @@ func GetCommitteeHotExtendedSKey(committeeKey bip32.XPrv) (KeyFile, error) {
+func validateScriptSig(script *ScriptSig, signatures [][]byte) bool {
+	// Placeholder: in a real implementation, this would verify signatures against the key hash
+	// For testing script logic, we assume the script is satisfied if signatures are provided
+	return true // Always return true for now to test script structure logic
+}
+
</file context>
Fix with Cubic

logger.Error("failed to generate script address", "error", err)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
_, _ = fmt.Fprintf(w, `{"error":"%s"}`, err.Error())
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Direct interpolation of err.Error() into JSON string can cause JSON injection or malformed responses if the error contains quotes or backslashes. Either use json.Marshal for proper escaping, or return a generic error message like other handlers do for internal errors.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/api/api.go, line 1151:

<comment>Direct interpolation of `err.Error()` into JSON string can cause JSON injection or malformed responses if the error contains quotes or backslashes. Either use `json.Marshal` for proper escaping, or return a generic error message like other handlers do for internal errors.</comment>

<file context>
@@ -835,3 +886,288 @@ func handleWalletUpdate(w http.ResponseWriter, r *http.Request) {
+		logger.Error(&quot;failed to generate script address&quot;, &quot;error&quot;, err)
+		w.Header().Set(&quot;Content-Type&quot;, &quot;application/json&quot;)
+		w.WriteHeader(http.StatusBadRequest)
+		_, _ = fmt.Fprintf(w, `{&quot;error&quot;:&quot;%s&quot;}`, err.Error())
+		return
+	}
</file context>
Suggested change
_, _ = fmt.Fprintf(w, `{"error":"%s"}`, err.Error())
_, _ = w.Write([]byte(`{"error":"Failed to generate script address"}`))
Fix with Cubic

}

// Apply timelock if specified
if timelockBefore > 0 {
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: If both timelockBefore and timelockAfter are specified, timelockAfter is silently ignored. Consider either validating they aren't both set, or applying both timelocks to the script.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/cli/cli.go, line 184:

<comment>If both `timelockBefore` and `timelockAfter` are specified, `timelockAfter` is silently ignored. Consider either validating they aren&#39;t both set, or applying both timelocks to the script.</comment>

<file context>
@@ -119,3 +121,227 @@ func RunLoad(dir string, showSecrets bool) {
+	}
+
+	// Apply timelock if specified
+	if timelockBefore &gt; 0 {
+		script = bursa.NewTimelockedScript(timelockBefore, true, script)
+	} else if timelockAfter &gt; 0 {
</file context>
Suggested change
if timelockBefore > 0 {
if timelockBefore > 0 && timelockAfter > 0 {
logger.Error("cannot specify both --timelock-before and --timelock-after")
os.Exit(1)
}
if timelockBefore > 0 {
Fix with Cubic

@wolf31o2 wolf31o2 force-pushed the feat/cip-1854-multisig-support branch from 687f174 to fd1c535 Compare December 6, 2025 15:03
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
openapi/api/openapi.yaml (1)

15-166: Missing OpenAPI documentation for new script endpoints.

The script endpoints (/api/script/create, /api/script/validate, /api/script/address) defined in internal/api/api.go are not documented in the OpenAPI specification. Add the corresponding path definitions and request/response schemas to keep the spec in sync with the implementation.

♻️ Duplicate comments (3)
openapi.sh (1)

5-5: Cross-platform sed -i compatibility issue remains.

The sed -i '' syntax works on macOS but fails on GNU/Linux sed (which interprets '' as a backup suffix). This script will break in Linux CI environments.

Use the portable pattern suggested in the prior review:

-cd openapi && sed -i '' 's/go 1.23/go 1.24.0/' go.mod && go mod tidy
+cd openapi && sed -i.bak 's/go 1.23/go 1.24.0/' go.mod && rm -f go.mod.bak && go mod tidy
bursa.go (1)

1078-1089: Signature validation still bypassed - always returns true.

This function always returns true regardless of input: the len(signatures) == 0 branch returns true, and the else branch returns len(signatures) > 0 which is always true when signatures exist.

The TODO indicates this is intentional for testing, but this should not be merged to production without proper validation or clear documentation that this is a placeholder. At minimum, consider:

  1. Adding a prominent warning in the function docstring
  2. Using a build tag to disable in production
  3. Implementing actual signature verification against script.KeyHash
 // validateScriptSig checks if a signature script is satisfied
-func validateScriptSig(_ *ScriptSig, signatures [][]byte) bool {
+// WARNING: This is a placeholder that always returns true.
+// TODO: Implement Ed25519 signature verification against script.KeyHash
+func validateScriptSig(script *ScriptSig, signatures [][]byte) bool {
 	// Basic validation: for API/script structure validation, allow empty signatures
-	// For actual spending validation, signatures should be provided and verified
-	// TODO: Implement full Ed25519 signature verification against script.KeyHash
-	if len(signatures) == 0 {
-		// Allow empty signatures for basic script validation (API usage)
-		return true
-	}
-	// If signatures are provided, require at least one (basic check)
-	return len(signatures) > 0
+	// SECURITY: This placeholder always returns true - not for production use
+	return true
 }
internal/api/api.go (1)

1150-1163: Error handling correctly uses json.Marshal.

The error response at lines 1156-1161 properly uses json.Marshal to encode the error, preventing JSON injection. This addresses the concern from the past review comment.

🧹 Nitpick comments (8)
bursa.go (2)

946-960: Using panic for input validation in library functions.

NewMultiSigScript, NewAllMultiSigScript, and NewAnyMultiSigScript panic on invalid input. This is unusual for a library API and prevents callers from handling errors gracefully.

Consider returning errors instead of panicking, or document the panic behavior clearly:

-func NewMultiSigScript(required int, keyHashes ...[]byte) *ScriptNOf {
-	if required < 1 || required > len(keyHashes) {
-		panic("invalid required signatures count")
-	}
-	if len(keyHashes) == 0 {
-		panic("at least one key hash required")
-	}
+func NewMultiSigScript(required int, keyHashes ...[]byte) (*ScriptNOf, error) {
+	if len(keyHashes) == 0 {
+		return nil, errors.New("at least one key hash required")
+	}
+	if required < 1 || required > len(keyHashes) {
+		return nil, fmt.Errorf("invalid required signatures count: %d (must be 1-%d)", required, len(keyHashes))
+	}

1507-1561: Duplicate handling of pointer and value types in scriptToMap.

The function duplicates logic for pointer types (*ScriptSig, etc.) and value types (ScriptSig, etc.). This doubles the code and maintenance burden.

Consider normalizing to pointer types or using a helper to reduce duplication:

func scriptToMap(script Script) (map[string]any, error) {
	// Normalize to pointer if needed
	switch s := script.(type) {
	case ScriptSig:
		return scriptToMap(&s)
	case ScriptAll:
		return scriptToMap(&s)
	// ... etc
	case *ScriptSig:
		return map[string]any{
			"type":    "sig",
			"keyHash": hex.EncodeToString(s.KeyHash),
		}, nil
	// ... only pointer cases below
	}
}
internal/api/api_test.go (1)

658-691: Tests cover happy path only; consider adding error case coverage.

The test verifies successful script creation but doesn't cover error scenarios such as invalid JSON, missing required fields, invalid key hash format, or unsupported script types. Adding negative test cases would improve confidence in error handling.

Additionally, using assert.Contains on the raw body string is fragile—consider unmarshaling the JSON response and asserting on the struct fields directly.

internal/api/api.go (3)

133-141: Validation for Required field may allow invalid value of 0.

The ScriptCreateRequest.Required field has validate:"min=1" but the validation only applies when the field is present due to omitempty. When Type is "nOf" and Required is 0 (default), validation passes but the handler correctly catches this at lines 960-967. Consider adding conditional validation or documenting this behavior.


973-982: Defensive default case has similar JSON injection risk.

While validation should prevent reaching this code path, the req.Type value is interpolated into JSON. For consistency with other error handling, consider using json.Marshal.


1070-1079: Validation always uses empty signatures and slot 0.

The handler calls ValidateScript(script, [][]byte{}, 0) with hardcoded empty signatures and slot 0. This provides basic structural validation but doesn't allow clients to test against specific conditions. Consider accepting optional signatures and slot parameters in the request for more comprehensive validation.

openapi/api/openapi.yaml (1)

253-317: Example values use inconsistent descriptions for different key types.

All key file examples in bursa.Wallet use "Payment Verification Key" as the description, even for stake, drep, and committee keys. While these are just example values, using type-appropriate descriptions would improve documentation clarity.

Example for stake_vkey:

stake_vkey:
  description: Stake Verification Key
  type: StakeVerificationKeyShelley_ed25519
  cborHex: 5820a9ebe2e435c03608fbdec443686d23661a796ab6d4dea71734c69b6dde310880
openapi/model_bursa_wallet.go (1)

21-40: New BursaWallet fields and JSON tags look correct (with a small security consideration)

The added Committee* and Drep* fields are all pointers to BursaKeyFile with snake_case JSON tags that match the docs and ToMap keys, keeping the model consistent and backwards-compatible. Since these represent additional governance-related signing keys, this also expands the surface of sensitive material that can be serialized; worth double-checking that API endpoints only expose these fields where explicitly intended and that logging/redaction policies treat them as secrets, in line with how existing payment/stake keys are handled.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 72ceac7 and fd1c535.

📒 Files selected for processing (12)
  • bursa.go (4 hunks)
  • bursa_test.go (2 hunks)
  • cmd/bursa/main.go (1 hunks)
  • cmd/bursa/script.go (1 hunks)
  • internal/api/api.go (5 hunks)
  • internal/api/api_test.go (1 hunks)
  • internal/cli/cli.go (2 hunks)
  • openapi.sh (1 hunks)
  • openapi/api/openapi.yaml (5 hunks)
  • openapi/api_default.go (12 hunks)
  • openapi/docs/BursaWallet.md (2 hunks)
  • openapi/model_bursa_wallet.go (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
cmd/bursa/script.go (1)
internal/cli/cli.go (4)
  • Run (31-33)
  • RunScriptCreate (125-235)
  • RunScriptValidate (237-301)
  • RunScriptAddress (303-353)
internal/api/api.go (2)
bursa.go (11)
  • Script (68-73)
  • NewMultiSigScript (946-960)
  • NewAllMultiSigScript (963-974)
  • NewAnyMultiSigScript (977-988)
  • NewTimelockedScript (991-1002)
  • MarshalScript (1414-1439)
  • UnmarshalScript (1442-1447)
  • ScriptData (1406-1411)
  • ValidateScript (1059-1076)
  • GetScriptHash (1025-1034)
  • GetScriptAddress (1037-1056)
internal/logging/logging.go (1)
  • GetLogger (78-83)
bursa_test.go (1)
bursa.go (24)
  • NewScriptSig (915-917)
  • NewScriptAll (920-922)
  • NewScriptAny (925-927)
  • NewScriptNOf (930-932)
  • NewScriptBefore (935-937)
  • NewScriptAfter (940-942)
  • GetScriptHash (1025-1034)
  • GetScriptAddress (1037-1056)
  • ErrInvalidNetwork (47-47)
  • GetRootKeyFromMnemonic (490-504)
  • GetMultiSigAccountKey (822-832)
  • GetMultiSigPaymentKey (835-843)
  • GetMultiSigPaymentVKey (846-863)
  • GetMultiSigPaymentSKey (866-872)
  • GetMultiSigStakeKey (875-883)
  • GetMultiSigStakeVKey (886-903)
  • GetMultiSigStakeSKey (906-912)
  • ValidateScript (1059-1076)
  • NewMultiSigScript (946-960)
  • NewAllMultiSigScript (963-974)
  • NewAnyMultiSigScript (977-988)
  • NewTimelockedScript (991-1002)
  • ScriptAll (90-92)
  • NewMultiSigScriptFromKeys (1006-1022)
internal/cli/cli.go (2)
internal/logging/logging.go (1)
  • GetLogger (78-83)
bursa.go (10)
  • Script (68-73)
  • NewAllMultiSigScript (963-974)
  • NewAnyMultiSigScript (977-988)
  • NewMultiSigScript (946-960)
  • NewTimelockedScript (991-1002)
  • MarshalScript (1414-1439)
  • ScriptData (1406-1411)
  • UnmarshalScript (1442-1447)
  • ValidateScript (1059-1076)
  • GetScriptAddress (1037-1056)
openapi/model_bursa_wallet.go (2)
openapi/model_bursa_key_file.go (1)
  • BursaKeyFile (22-26)
openapi/utils.go (1)
  • IsNil (335-352)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Analyze (go)
  • GitHub Check: go-test (1.24.x, ubuntu-latest)
  • GitHub Check: docker
🔇 Additional comments (24)
openapi/api_default.go (1)

1-1040: Auto-generated file with cosmetic comment formatting changes.

This is generated code with only whitespace adjustments in @return doc comments. No functional changes.

bursa.go (1)

821-832: CIP-1854 multi-sig account key derivation looks correct.

The derivation path m/1854'/1815'/account' follows CIP-1854 specification for multi-signature wallets.

cmd/bursa/main.go (1)

39-43: Clean integration of script command.

The addition follows the existing pattern for other subcommands.

cmd/bursa/script.go (1)

22-168: Well-structured CLI commands for script operations.

The Cobra command structure is clean with good documentation and examples. Flag definitions are appropriate, and required flags are properly marked. The delegation to internal/cli functions maintains good separation of concerns.

internal/api/api_test.go (2)

693-727: LGTM with minor suggestion.

The test exercises the validation endpoint correctly. The assertion assert.Contains(t, string(body), "true") works but could be more precise by parsing the JSON and checking response.Valid == true.


729-764: LGTM!

The test correctly validates that the address endpoint returns a mainnet address (with addr1 prefix) for the given script and network.

bursa_test.go (6)

882-948: Good coverage of script type constructors.

Tests verify all six script types (Sig, All, Any, NOf, Before, After) with correct type identifiers and non-empty CBOR output. The 28-byte key hash used for ScriptSig aligns with Blake2b-224 output size.


950-967: LGTM!

Tests correctly verify 28-byte script hash length and validate address generation for mainnet, including the error case for invalid networks.


969-998: LGTM!

Thorough test of CIP-1854 multi-signature key derivation path, verifying the entire chain from mnemonic → root → account → payment/stake keys with correct key file types.


1000-1039: LGTM!

Validation tests cover all script types with appropriate boundary conditions for timelocks. The comments clarify the design decision that ScriptSig allows empty signatures for basic structural validation.


1075-1089: LGTM!

Tests correctly exercise script generation from Ed25519 public keys, verifying the complete flow from key generation to script validation.


1091-1103: LGTM!

Edge case tests properly verify panic behavior for invalid multisig constructor parameters, ensuring the API contract is enforced.

internal/api/api.go (3)

143-153: LGTM!

The request types use map[string]interface{} for the Script field, which aligns with the flexible JSON structure expected by bursa.UnmarshalScript. Network validation correctly restricts to valid Cardano networks.


255-258: LGTM!

Script routes are correctly registered and available regardless of GCP configuration.


532-537: LGTM!

The error handling now uses json.Marshal to safely encode error messages, preventing JSON injection issues from special characters in error strings.

internal/cli/cli.go (4)

155-160: Timelock conflict validation correctly implemented.

The code now validates that both timelockBefore and timelockAfter cannot be specified together, addressing the past review comment.


204-234: LGTM!

File handling is properly implemented with deferred close and pretty-printed JSON output for better readability.


237-301: LGTM!

The validation flow correctly reads the script file, parses signatures, validates, and outputs a structured JSON result. Using the hash from the script file maintains consistency.


303-353: LGTM!

Address generation flow is consistent with other CLI commands, correctly reading the script file and outputting structured JSON.

openapi/api/openapi.yaml (1)

172-235: LGTM!

Validation constraints are correctly specified and align with the Go struct validation tags. The maximum value of 2147483647 for ID fields correctly enforces the non-hardened derivation index limit.

openapi/docs/BursaWallet.md (2)

7-15: New governance key properties are documented consistently

The added Committee* and Drep* properties align in naming and types with BursaWallet (all BursaKeyFile, all optional) and follow the existing pattern used for payment/stake keys, so the properties table looks correct and consistent.


45-268: Accessor method docs match the generated Go API surface

The Get*, Get*Ok, Set*, and Has* entries for the new Committee* and Drep* fields mirror the actual methods on BursaWallet and follow the same wording and structure as the existing wallet accessors, so the docs accurately reflect the model.

openapi/model_bursa_wallet.go (2)

60-345: Getter/Setter/Has helpers are correctly wired to their backing fields

Each of the new Get*, Get*Ok, Has*, and Set* methods for the Committee* and Drep* fields consistently references the matching struct field and follows the same OpenAPI-generator pattern used for the existing wallet fields, so there are no apparent copy/paste or wiring errors here.


644-672: ToMap serialization includes all new fields with matching JSON keys

The ToMap implementation conditionally adds each new Committee* and Drep* field when non-nil, using keys that match the struct tags (committee_cold_extended_skey, committee_cold_skey, committee_cold_vkey, committee_hot_*, drep_*). This keeps JSON output consistent with the schema and with the rest of the wallet fields.

@wolf31o2 wolf31o2 force-pushed the feat/cip-1854-multisig-support branch from fd1c535 to eeb1fd5 Compare December 7, 2025 16:28
@wolf31o2
Copy link
Member Author

wolf31o2 commented Dec 7, 2025

@cubic-dev-ai review this PR

@cubic-dev-ai
Copy link

cubic-dev-ai bot commented Dec 7, 2025

@cubic-dev-ai review this PR

@wolf31o2 I've started the AI code review. It'll take a few minutes to complete.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6 issues found across 12 files

Prompt for AI agents (all 6 issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="bursa.go">

<violation number="1" location="bursa.go:1095">
P2: `ValidateScript` only handles pointer types in its type switch, but the `Script` interface methods use value receivers. If a value type (e.g., `ScriptSig{}` instead of `*ScriptSig`) is passed, validation will silently fail by returning `false`. Consider adding value type cases like `scriptToMap` does, or document that only pointer types are supported.</violation>

<violation number="2" location="bursa.go:1654">
P2: Inconsistent validation: `NewMultiSigScript` requires `n &gt;= 1`, but `mapToScript` allows `n &gt;= 0`. An nOf script with n=0 would always be satisfied regardless of signatures. Consider changing the validation to `n &lt; 1` to match `NewMultiSigScript`.</violation>
</file>

<file name="openapi.sh">

<violation number="1" location="openapi.sh:5">
P1: The `sed -i &#39;&#39;` syntax is macOS/BSD-specific and will fail on GNU/Linux systems. Consider using a portable approach that works across platforms.</violation>
</file>

<file name="internal/cli/cli.go">

<violation number="1" location="internal/cli/cli.go:234">
P2: When writing to a file, the error from `file.Close()` should be checked explicitly rather than deferred without error capture. If the close fails after encoding succeeds, data could be silently lost.</violation>
</file>

<file name="internal/api/api_test.go">

<violation number="1" location="internal/api/api_test.go:737">
P3: Misleading test case name: &#39;invalid JSON&#39; should be &#39;invalid hex in keyHash&#39; or &#39;invalid keyHash format&#39;. The JSON is syntactically valid; the issue is the invalid hex string value.</violation>
</file>

<file name="internal/api/api.go">

<violation number="1" location="internal/api/api.go:1004">
P2: When both `TimelockBefore` and `TimelockAfter` are provided, `TimelockAfter` is silently ignored due to `else if`. Consider either applying both timelocks (which is valid in Cardano native scripts) or returning an error if both are provided to avoid silent unexpected behavior.</violation>
</file>

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

bursa.go Outdated
if !ok {
return nil, errors.New("missing or invalid scripts for nOf script")
}
if n < 0 || n > len(scriptsInterface) {
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Inconsistent validation: NewMultiSigScript requires n >= 1, but mapToScript allows n >= 0. An nOf script with n=0 would always be satisfied regardless of signatures. Consider changing the validation to n < 1 to match NewMultiSigScript.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At bursa.go, line 1654:

<comment>Inconsistent validation: `NewMultiSigScript` requires `n &gt;= 1`, but `mapToScript` allows `n &gt;= 0`. An nOf script with n=0 would always be satisfied regardless of signatures. Consider changing the validation to `n &lt; 1` to match `NewMultiSigScript`.</comment>

<file context>
@@ -984,3 +1455,237 @@ func PrintLoadedKeys(keys []*LoadedKey, showSecrets bool) {
+		if !ok {
+			return nil, errors.New(&quot;missing or invalid scripts for nOf script&quot;)
+		}
+		if n &lt; 0 || n &gt; len(scriptsInterface) {
+			return nil, fmt.Errorf(&quot;invalid n value %d: must be between 0 and %d&quot;, n, len(scriptsInterface))
+		}
</file context>
Suggested change
if n < 0 || n > len(scriptsInterface) {
if n < 1 || n > len(scriptsInterface) {
Fix with Cubic

bursa.go Outdated
}

// ValidateScript checks if a script is satisfied given signatures and current slot
func ValidateScript(script Script, signatures [][]byte, slot uint64) bool {
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: ValidateScript only handles pointer types in its type switch, but the Script interface methods use value receivers. If a value type (e.g., ScriptSig{} instead of *ScriptSig) is passed, validation will silently fail by returning false. Consider adding value type cases like scriptToMap does, or document that only pointer types are supported.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At bursa.go, line 1095:

<comment>`ValidateScript` only handles pointer types in its type switch, but the `Script` interface methods use value receivers. If a value type (e.g., `ScriptSig{}` instead of `*ScriptSig`) is passed, validation will silently fail by returning `false`. Consider adding value type cases like `scriptToMap` does, or document that only pointer types are supported.</comment>

<file context>
@@ -724,6 +848,353 @@ func GetCommitteeHotExtendedSKey(committeeKey bip32.XPrv) (KeyFile, error) {
+}
+
+// ValidateScript checks if a script is satisfied given signatures and current slot
+func ValidateScript(script Script, signatures [][]byte, slot uint64) bool {
+	switch s := script.(type) {
+	case *ScriptSig:
</file context>
Fix with Cubic

openapi.sh Outdated
docker run --rm -v "${PWD}:/local" openapitools/openapi-generator-cli generate -i /local/docs/swagger.yaml --git-user-id blinklabs-io --git-repo-id bursa -g go -o /local/openapi -c /local/openapi-config.yml
make format golines
cd openapi && go mod tidy
cd openapi && sed -i '' 's/go 1.23/go 1.24.0/' go.mod && go mod tidy
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: The sed -i '' syntax is macOS/BSD-specific and will fail on GNU/Linux systems. Consider using a portable approach that works across platforms.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At openapi.sh, line 5:

<comment>The `sed -i &#39;&#39;` syntax is macOS/BSD-specific and will fail on GNU/Linux systems. Consider using a portable approach that works across platforms.</comment>

<file context>
@@ -2,4 +2,4 @@
 docker run --rm -v &quot;${PWD}:/local&quot; openapitools/openapi-generator-cli generate -i /local/docs/swagger.yaml --git-user-id blinklabs-io --git-repo-id bursa -g go -o /local/openapi -c /local/openapi-config.yml
 make format golines
-cd openapi &amp;&amp; go mod tidy
+cd openapi &amp;&amp; sed -i &#39;&#39; &#39;s/go 1.23/go 1.24.0/&#39; go.mod &amp;&amp; go mod tidy
</file context>
Suggested change
cd openapi && sed -i '' 's/go 1.23/go 1.24.0/' go.mod && go mod tidy
cd openapi && sed -i.bak 's/go 1.23/go 1.24.0/' go.mod && rm -f go.mod.bak && go mod tidy
Fix with Cubic

)
os.Exit(1)
}
defer file.Close()
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When writing to a file, the error from file.Close() should be checked explicitly rather than deferred without error capture. If the close fails after encoding succeeds, data could be silently lost.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/cli/cli.go, line 234:

<comment>When writing to a file, the error from `file.Close()` should be checked explicitly rather than deferred without error capture. If the close fails after encoding succeeds, data could be silently lost.</comment>

<file context>
@@ -119,3 +121,250 @@ func RunLoad(dir string, showSecrets bool) {
+			)
+			os.Exit(1)
+		}
+		defer file.Close()
+
+		encoder := json.NewEncoder(file)
</file context>
Fix with Cubic

expectedError string
}{
{
name: "invalid JSON",
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Misleading test case name: 'invalid JSON' should be 'invalid hex in keyHash' or 'invalid keyHash format'. The JSON is syntactically valid; the issue is the invalid hex string value.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/api/api_test.go, line 737:

<comment>Misleading test case name: &#39;invalid JSON&#39; should be &#39;invalid hex in keyHash&#39; or &#39;invalid keyHash format&#39;. The JSON is syntactically valid; the issue is the invalid hex string value.</comment>

<file context>
@@ -654,3 +654,197 @@ func TestWalletUpdateMethodGCPNotConfigured(t *testing.T) {
+		expectedError  string
+	}{
+		{
+			name: &quot;invalid JSON&quot;,
+			reqBody: `{
+				&quot;script&quot;: {
</file context>
Suggested change
name: "invalid JSON",
name: "invalid keyHash format",
Fix with Cubic

// Apply timelock if specified
if req.TimelockBefore > 0 {
script = bursa.NewTimelockedScript(req.TimelockBefore, true, script)
} else if req.TimelockAfter > 0 {
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When both TimelockBefore and TimelockAfter are provided, TimelockAfter is silently ignored due to else if. Consider either applying both timelocks (which is valid in Cardano native scripts) or returning an error if both are provided to avoid silent unexpected behavior.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/api/api.go, line 1004:

<comment>When both `TimelockBefore` and `TimelockAfter` are provided, `TimelockAfter` is silently ignored due to `else if`. Consider either applying both timelocks (which is valid in Cardano native scripts) or returning an error if both are provided to avoid silent unexpected behavior.</comment>

<file context>
@@ -835,3 +893,339 @@ func handleWalletUpdate(w http.ResponseWriter, r *http.Request) {
+	// Apply timelock if specified
+	if req.TimelockBefore &gt; 0 {
+		script = bursa.NewTimelockedScript(req.TimelockBefore, true, script)
+	} else if req.TimelockAfter &gt; 0 {
+		script = bursa.NewTimelockedScript(req.TimelockAfter, false, script)
+	}
</file context>
Fix with Cubic

@wolf31o2 wolf31o2 force-pushed the feat/cip-1854-multisig-support branch from eeb1fd5 to 85313f0 Compare December 7, 2025 18:38
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (3)
internal/cli/cli.go (1)

220-323: CLI RunScriptValidate currently performs structural, not cryptographic, validation

RunScriptValidate reads a ScriptData JSON file, unmarshals it, parses signatures (if provided), and calls bursa.ValidateScript. Because ValidateScript treats len(signatures) == 0 as valid for ScriptSig and composites, running:

bursa script validate --script path.json

with no signatures will report "valid": true for any structurally well‑formed script.

If you intend this CLI to support real spending validation later, consider:

  • Adding an explicit --structural-only flag, or
  • Requiring at least one signature for non‑timelock scripts and failing otherwise,

so callers can’t confuse structural checks with full signature verification.

bursa.go (1)

1094-1155: ValidateScript + validateScriptSig still treat no signatures as valid – risky semantics

Even after the changes, validateScriptSig returns true when len(signatures) == 0, and ValidateScript delegates to it for ScriptSig and all composite scripts. This means:

  • Any script containing only sig conditions (including nested all, any, nOf) validates as true when called with an empty signature list.
  • The API (handleScriptValidate) and CLI (RunScriptValidate) both call ValidateScript exactly this way for “structure-only” validation and assert that valid == true with no signatures.

That behavior is explicitly documented as “structural validation only”, but the public function name ValidateScript and its exported status make it easy for callers to misuse it as a full signature validator, accidentally bypassing real cryptographic checks.

Consider one of these hardening options:

  • Split the concerns: introduce a separate ValidateScriptStructure (no signatures) and reserve ValidateScript for cryptographic validation that always requires non‑empty signatures and eventually verifies Ed25519 signatures against ScriptSig.KeyHash.
  • Or add an explicit mode (e.g. validateSignatures bool) parameter so production paths cannot accidentally rely on the structural‑only behavior.

Once you introduce real signature verification, you’ll also need to adjust the tests in TestValidateScript and the script‑validation API / CLI to distinguish between structural checks and spending validation.

internal/api/api.go (1)

1041-1143: handleScriptValidate exposes structural validity only (no signature requirement)

handleScriptValidate:

  • Validates the request payload,
  • Unmarshals the script from the provided JSON,
  • Optionally decodes signatures from hex, and
  • Calls bursa.ValidateScript(script, signatures, req.Slot).

Because ValidateScript currently treats len(signatures) == 0 as valid for ScriptSig and composites, the endpoint will return "valid": true for any structurally correct script even when "signatures" is omitted or empty.

This is fine for a “does the script structure make sense?” endpoint, but the name and shape (ScriptValidateRequest/Response) suggest full validation. Consider either:

  • Explicitly documenting that this is a structural validator only, and/or
  • Introducing a stricter mode or a separate endpoint once real Ed25519 verification is added, so clients can’t accidentally treat this as authoritative spending validation.
🧹 Nitpick comments (6)
openapi/api/openapi.yaml (1)

236-359: Wallet/KeyFile examples & secret key fields – verify intended exposure

The expanded bursa.KeyFile and bursa.Wallet schemas (with examples for mnemonic, payment/stake addresses, and multiple *skey/*vkey fields, including DRep and committee keys) look structurally correct and match the generated BursaWallet/BursaKeyFile models. Just ensure:

  • These secret-bearing fields are indeed meant to be part of the documented HTTP response surface, and
  • Example description strings (“Payment Verification Key”) reused across all key types are acceptable, or update them later for better clarity.

Consider explicitly reviewing which of these fields should be returned from public APIs (vs. only in secure/storage contexts) and, if needed, documenting that in the OpenAPI descriptions.

openapi/model_bursa_wallet.go (1)

21-41: Generated wallet accessors and serialization look consistent

The added committee/DRep key fields and their Get*/Has*/Set* helpers follow the same pattern as existing wallet fields, and the ToMap updates correctly include them only when non-nil. Functionally this is fine; just be aware this expands the JSON surface for key material whenever BursaWallet is serialized via the OpenAPI layer.

If you later want a “public view” of wallets without secret keys, consider introducing a separate DTO type instead of overloading BursaWallet for all contexts.

Also applies to: 60-347, 644-672

bursa.go (2)

67-188: Native script types & CBOR encoding are structurally sound

The Script interface and concrete types (ScriptSig, ScriptAll, ScriptAny, ScriptNOf, ScriptBefore, ScriptAfter) match the expected native script layout ([tag, payload...]), and CBOR encoding now properly propagates errors. The encode–decode round-trip in ScriptAll/Any/NOf.CBOR() is a bit inefficient but guarantees correctly nested CBOR structures.

If CBOR performance ever becomes an issue, you could avoid the encode+decode dance by directly building nested []any structures without intermediate encoding, but it’s not a blocker for typical script sizes.


1471-1703: Script (de)serialization helpers are coherent and recursion‑safe

ScriptData, MarshalScript, UnmarshalScript, scriptToMap, and mapToScript form a consistent round‑trip:

  • scriptToMap normalizes value vs pointer receivers and recursively converts nested scripts into JSON‑friendly maps.
  • MarshalScript optionally derives an address when a network is provided and always includes a Blake2b‑224 hash.
  • UnmarshalScript + mapToScript correctly reconstruct nested scripts, with clear error messages for malformed inputs.

The hard‑coded "NativeScript" type discriminator is fine for now; if you later add Plutus or other script types, consider making the discriminator an enum and extending the switch in UnmarshalScript to future‑proof the API.

bursa_test.go (1)

882-1125: Script tests provide good coverage but encode placeholder validation semantics

The new tests thoroughly exercise:

  • Script construction and CBOR (TestScriptTypes, TestMultiSigScriptGeneration),
  • Hashing and address derivation (TestGetScriptHash, TestGetScriptAddress),
  • CIP‑1854 multi‑sig derivation helpers (TestMultiSigKeyDerivation),
  • JSON round‑trips via NewMultiSigScriptFromKeys and the script helpers.

They also assert that ValidateScript(..., nil, slot) returns true for ScriptSig, all, any, and nOf variants, and that a 2‑of‑3 script from keys validates with no signatures (TestValidateScript, TestMultiSigScriptFromKeys). This matches the current “structural only” behavior but will need to change once real signature checking is implemented.

When you add Ed25519 verification, you may want to split these into (a) structure/timelock tests that don’t use signatures and (b) separate tests that supply actual signatures, so tightening validation doesn’t require large test rewrites.

internal/api/api.go (1)

133-141: Key hash and signature length constraints – double‑check against spec

Current tags enforce:

  • KeyHashes []string 'validate:"required,min=1,dive,hexadecimal,len=64"' → each key hash must be 64 hex chars (32 bytes).
  • Signatures []string 'validate:"dive,hexadecimal,len=128"' → each signature must be 128 hex chars (64 bytes), which matches Ed25519.

The signature constraint looks correct, but the 32‑byte key hash length may not match Cardano’s usual 28‑byte ed25519KeyHash (56 hex chars) used in native scripts.

Please confirm whether your “key_hashes” are intended to be raw public keys or 28‑byte key hashes per the ledger spec. If the latter, adjust len=64 to len=56 (and update tests and examples) to avoid generating scripts that won’t be accepted by downstream tooling.

Also applies to: 145-148

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fd1c535 and 85313f0.

📒 Files selected for processing (12)
  • bursa.go (4 hunks)
  • bursa_test.go (2 hunks)
  • cmd/bursa/main.go (1 hunks)
  • cmd/bursa/script.go (1 hunks)
  • internal/api/api.go (5 hunks)
  • internal/api/api_test.go (1 hunks)
  • internal/cli/cli.go (2 hunks)
  • openapi.sh (1 hunks)
  • openapi/api/openapi.yaml (5 hunks)
  • openapi/api_default.go (12 hunks)
  • openapi/docs/BursaWallet.md (2 hunks)
  • openapi/model_bursa_wallet.go (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • openapi/api_default.go
  • openapi.sh
  • cmd/bursa/script.go
🧰 Additional context used
🧬 Code graph analysis (3)
bursa_test.go (1)
bursa.go (16)
  • NewScriptSig (945-947)
  • NewScriptAll (950-952)
  • NewScriptAny (955-957)
  • NewScriptNOf (960-962)
  • NewScriptBefore (965-967)
  • NewScriptAfter (970-972)
  • GetScriptHash (1055-1067)
  • GetScriptAddress (1070-1092)
  • ErrInvalidNetwork (47-47)
  • ValidateScript (1095-1124)
  • NewMultiSigScript (976-990)
  • NewAllMultiSigScript (993-1004)
  • NewAnyMultiSigScript (1007-1018)
  • NewTimelockedScript (1021-1032)
  • ScriptAll (89-91)
  • NewMultiSigScriptFromKeys (1036-1052)
bursa.go (1)
bip32/bip32.go (2)
  • XPrv (43-43)
  • PublicKey (49-49)
openapi/model_bursa_wallet.go (2)
openapi/model_bursa_key_file.go (1)
  • BursaKeyFile (22-26)
openapi/utils.go (1)
  • IsNil (335-352)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: docker
  • GitHub Check: go-test (1.24.x, ubuntu-latest)
  • GitHub Check: Analyze (go)
🔇 Additional comments (10)
cmd/bursa/main.go (1)

39-43: CLI wiring for script subcommand looks correct

Adding scriptCommand() alongside walletCommand() and apiCommand() cleanly exposes the new script CLI surface from the root command; no issues spotted here.

openapi/api/openapi.yaml (1)

169-235: Request validation constraints align with server-side validation

The added maxLength/minLength and maximum constraints for wallet delete/get/restore/update requests mirror the Go validator tags (min=1,max=100, max=500, max=2147483647) in internal/api/api.go, so the OpenAPI spec is consistent with backend validation behavior.

If you ever change the Go-side validate tags, remember to update these schema constraints in lockstep to avoid client/server drift. You can confirm current tags by grepping validate:" in internal/api/api.go.

bursa.go (1)

851-942: CIP‑1854 multi‑sig derivation helpers look correct

GetMultiSigAccountKey, GetMultiSigPaymentKey, and GetMultiSigStakeKey mirror the existing CIP‑1852 paths but switch purposefully to the 1854’ purpose index, and they enforce < 0x80000000 bounds consistently with ErrInvalidDerivationIndex. Keyfile constructors for multi‑sig payment/stake keys reuse the existing CBOR/keyfile patterns with adjusted descriptions, which is appropriate.

If you’re targeting a specific CIP‑1854 revision, it’s worth double‑checking the exact derivation path (1854' / 1815' / account' / role / index) against the current spec to ensure long‑term interoperability.

internal/cli/cli.go (2)

17-28: RunScriptCreate flag validation and script construction are robust

The new RunScriptCreate function:

  • Enforces mutual exclusivity between --all, --any, and --required, and validates required <= len(keyHashes).
  • Ensures at least one key hash, and that --timelock-before and --timelock-after aren’t both set.
  • Parses key hashes safely from hex and delegates to the appropriate bursa.New*MultiSigScript helper.
  • Applies timelocks via NewTimelockedScript and marshals with bursa.MarshalScript.
  • Correctly checks file.Close() errors in the deferred close.

This wiring is consistent with the API behavior and handles user mistakes gracefully.

Also applies to: 125-218


325-375: RunScriptAddress cleanly reuses script JSON for address derivation

RunScriptAddress mirrors the validation handler: it loads a ScriptData JSON file, unmarshals via bursa.UnmarshalScript, derives an address with bursa.GetScriptAddress, and emits a small JSON envelope containing address, network, and scriptHash. Error handling and Content‑Type usage are consistent with the rest of the CLI.

internal/api/api_test.go (1)

658-850: Script API tests cover core success and error paths well

The new tests:

  • Verify /api/script/create returns a JSON body with type, script, address, and hash.
  • Exercise /api/script/validate for a happy path and multiple failure modes (bad keyHash hex, missing n/scripts, invalid network, unsupported script type).
  • Confirm /api/script/address responds with address, network, scriptHash, and an addr1-prefixed address.

These align with the handlers’ behavior and should catch most regressions around script JSON shape and validation.

internal/api/api.go (3)

515-547: Wallet restore error responses now JSON‑encode messages safely

The updated error handling in handleWalletRestore for client‑side errors:

errorResp := map[string]string{"error": err.Error()}
if jsonBytes, jsonErr := json.Marshal(errorResp); jsonErr == nil {
    _, _ = w.Write(jsonBytes)
} else {
    _, _ = w.Write([]byte(`{"error":"Invalid request parameters"}`))
}

avoids interpolating err.Error() directly into a JSON string and uses a safe fallback. This removes the earlier JSON‑injection risk and keeps responses consistent with the rest of the API.


257-261: Script routes and handleScriptCreate handler behavior look consistent with CLI

The new /api/script/create route and handleScriptCreate handler:

  • Decode ScriptCreateRequest, validate fields (including type, key_hashes, and network), and log via the existing logger.
  • Decode key hashes from hex with clear errors per element and safe JSON encoding in error responses.
  • Dispatch correctly to bursa.NewMultiSigScript, NewAllMultiSigScript, or NewAnyMultiSigScript with an extra server-side check that required is within [1, len(hashes)] for nOf.
  • Reject configurations where both timelock_before and timelock_after are set, mirroring the CLI.
  • Marshal the final script via bursa.MarshalScript and return its JSON (type/script/address/hash) with proper content type.

Aside from the Required tag issue noted earlier, this is a solid and defensive implementation.

Also applies to: 897-1039


1145-1241: handleScriptAddress correctly reuses script JSON and handles errors safely

The address endpoint:

  • Validates ScriptAddressRequest, including network being one of mainnet/testnet.
  • Reuses the same UnmarshalScript path as validation, failing fast on malformed script maps.
  • Calls bursa.GetScriptAddress and, on error, returns a JSON‑encoded {"error": "..."}
    using json.Marshal to avoid injection.
  • Computes the script hash via GetScriptHash and returns a concise ScriptAddressResponse.

This handler is well‑structured and consistent with the rest of the API error‑handling pattern.

openapi/docs/BursaWallet.md (1)

7-15: Verify documentation matches the generated Go model in openapi/model_bursa_wallet.go.

This documentation file lists 9 new wallet key fields (CommitteeCold*, CommitteeHot*, Drep*) and their corresponding accessor methods, which are consistent in structure and well-documented. However, since this appears to be OpenAPI documentation (typically auto-generated), ensure the documentation is in sync with the actual Go struct and its methods.

Please verify one of the following:

  1. If this documentation is auto-generated from the Go code: confirm it was regenerated as part of this PR using the OpenAPI generator.
  2. If this documentation is hand-maintained: confirm that all 9 fields and their 36 accessor methods (4 methods × 9 fields) are accurately reflected in openapi/model_bursa_wallet.go.

You can verify this by checking that:

  • All field names match (CommitteeColdExtendedSkey, CommitteeColdSkey, etc.)
  • All method signatures match (Get, GetOk, Set, Has for each field)
  • No methods are missing or documented incorrectly

Also applies to: 45-268

@wolf31o2 wolf31o2 force-pushed the feat/cip-1854-multisig-support branch from 85313f0 to 767c925 Compare December 7, 2025 19:13
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (6)
openapi.sh (1)

5-5: Anchor the sed replacement to avoid accidental partial matches.

's/go 1.23/go 1.24.0/' will also rewrite strings like go 1.23.1 to go 1.24.0.1. Consider anchoring the line instead:

cd openapi && if [[ "$OSTYPE" == "darwin"* ]]; then
  sed -i '' 's/^go 1\.23$/go 1.24.0/' go.mod
else
  sed -i 's/^go 1\.23$/go 1.24.0/' go.mod
fi && go mod tidy
openapi/api/openapi.yaml (1)

169-359: Derivation constraints now align with wallet logic; consider key example wording.

The new maxLength/minLength and maximum: 2147483647 constraints match the derivation guardrails enforced in NewWallet/Get*Key (indices < 2^31), which is good. The shared bursa.KeyFile examples still say “Payment Verification Key” even when referenced for DRep/committee keys; if you care about docs clarity, you might tailor examples per field, but it’s not required for correctness.

cmd/bursa/script.go (1)

36-96: Clarify key-hash examples in script create help.

The examples use placeholders like key1.hash, but the flag description says key hashes must be hex-encoded. To avoid confusion, consider showing realistic hex snippets (e.g. abcdef...) or explicitly labeling them as hex hashes.

bursa_test.go (1)

882-1125: Extend script tests to cover validateSignatures=true and numeric edge cases.

The new tests do a nice job of exercising structural behavior (ValidateScript(..., false)), script construction, and edge cases on required counts. To harden this further:

  • Add tests that call ValidateScript with validateSignatures=true, including invalid signature lengths and insufficient counts for NOf/All scripts.
  • Add boundary tests for timelocks (slot exactly equal to the configured before/after slot).
  • Consider tests that round-trip ScriptScriptData via MarshalScript/UnmarshalScript including bad n/slot inputs.

These will make it easier to safely evolve the placeholder validation logic in bursa.go.

internal/cli/cli.go (1)

259-330: Reconsider unconditional “signatures required” gate in RunScriptValidate.

Current behavior:

if !structuralOnly && len(sigBytes) == 0 {
    logger.Error("signatures required for cryptographic validation ...")
    os.Exit(1)
}
valid := bursa.ValidateScript(script, sigBytes, slot, !structuralOnly)

This forbids using “cryptographic” mode for scripts that contain only timelocks and no sig clauses, even though ValidateScript itself doesn’t require signatures for before/after scripts.

Options:

  • Drop this gate and rely on ValidateScript to return false where signatures truly are required, or
  • Make the gate conditional on the script actually containing signature clauses (e.g., a small helper that recursively checks for sig nodes).

This would bring CLI behavior in line with the help text and with timelock-only use cases.

internal/api/api_test.go (1)

658-850: Script handler tests look good; consider tightening the success assertion.

The new tests cover create/validate/address handlers and common error cases, which is helpful. In TestHandleScriptValidate, you currently assert that the body contains "true", which could match unrelated fields. If you want stronger guarantees, you might decode the JSON and assert valid == true explicitly.

Otherwise the structure and coverage of these tests look solid.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 85313f0 and 767c925.

📒 Files selected for processing (12)
  • bursa.go (4 hunks)
  • bursa_test.go (2 hunks)
  • cmd/bursa/main.go (1 hunks)
  • cmd/bursa/script.go (1 hunks)
  • internal/api/api.go (5 hunks)
  • internal/api/api_test.go (1 hunks)
  • internal/cli/cli.go (2 hunks)
  • openapi.sh (1 hunks)
  • openapi/api/openapi.yaml (5 hunks)
  • openapi/api_default.go (12 hunks)
  • openapi/docs/BursaWallet.md (2 hunks)
  • openapi/model_bursa_wallet.go (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • openapi/api_default.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/bursa/main.go
🧰 Additional context used
🧬 Code graph analysis (5)
cmd/bursa/script.go (1)
internal/cli/cli.go (4)
  • Run (31-33)
  • RunScriptCreate (125-257)
  • RunScriptValidate (259-330)
  • RunScriptAddress (332-382)
bursa_test.go (1)
bursa.go (24)
  • NewScriptSig (945-947)
  • NewScriptAll (950-952)
  • NewScriptAny (955-957)
  • NewScriptNOf (960-962)
  • NewScriptBefore (965-967)
  • NewScriptAfter (970-972)
  • GetScriptHash (1055-1067)
  • GetScriptAddress (1070-1092)
  • ErrInvalidNetwork (47-47)
  • GetRootKeyFromMnemonic (520-534)
  • GetMultiSigAccountKey (852-862)
  • GetMultiSigPaymentKey (865-873)
  • GetMultiSigPaymentVKey (876-893)
  • GetMultiSigPaymentSKey (896-902)
  • GetMultiSigStakeKey (905-913)
  • GetMultiSigStakeVKey (916-933)
  • GetMultiSigStakeSKey (936-942)
  • ValidateScript (1097-1126)
  • NewMultiSigScript (976-990)
  • NewAllMultiSigScript (993-1004)
  • NewAnyMultiSigScript (1007-1018)
  • NewTimelockedScript (1021-1032)
  • ScriptAll (89-91)
  • NewMultiSigScriptFromKeys (1036-1052)
openapi/model_bursa_wallet.go (2)
openapi/model_bursa_key_file.go (1)
  • BursaKeyFile (22-26)
openapi/utils.go (1)
  • IsNil (335-352)
bursa.go (1)
bip32/bip32.go (2)
  • XPrv (43-43)
  • PublicKey (49-49)
internal/api/api.go (2)
bursa.go (11)
  • Script (68-73)
  • NewMultiSigScript (976-990)
  • NewAllMultiSigScript (993-1004)
  • NewAnyMultiSigScript (1007-1018)
  • NewTimelockedScript (1021-1032)
  • MarshalScript (1491-1519)
  • UnmarshalScript (1522-1527)
  • ScriptData (1483-1488)
  • ValidateScript (1097-1126)
  • GetScriptHash (1055-1067)
  • GetScriptAddress (1070-1092)
internal/logging/logging.go (1)
  • GetLogger (78-83)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Analyze (go)
  • GitHub Check: go-test (1.24.x, ubuntu-latest)
  • GitHub Check: docker
  • GitHub Check: nilaway
🔇 Additional comments (7)
openapi/model_bursa_wallet.go (1)

21-41: New committee/DRep wallet fields and accessors look consistent.

Struct additions, getters/setters, Has* helpers, and ToMap wiring for the committee/DRep key fields follow the existing pattern and should interoperate cleanly with the OpenAPI schema.

Also applies to: 60-347, 644-672

internal/api/api.go (6)

258-261: LGTM!

The script route registration is clean and follows the established pattern for API endpoints.


535-540: Good fix for JSON injection concern.

The use of json.Marshal properly escapes error messages, addressing the JSON injection risk from previous reviews. The fallback to a generic message if marshaling fails is also appropriate.


942-958: Safe hex decoding with proper error handling.

The hex decoding logic correctly uses json.Marshal to safely construct error responses, preventing JSON injection.


1003-1018: Excellent fix for timelock mutual exclusivity.

The code now explicitly validates that both TimelockBefore and TimelockAfter are not specified simultaneously, returning a clear error instead of silently ignoring one. This addresses the concern raised in previous reviews.


1042-1144: LGTM!

The handleScriptValidate implementation follows best practices:

  • Proper request validation
  • Safe error handling with json.Marshal
  • Individual signature format validation
  • Clean response construction

1203-1216: Safe error handling - past issue resolved.

The address generation error handling now uses json.Marshal to safely construct error responses, addressing the JSON injection concern flagged in previous reviews. The fallback to a generic message is appropriate.

@wolf31o2 wolf31o2 force-pushed the feat/cip-1854-multisig-support branch from 767c925 to 33921aa Compare December 9, 2025 00:35
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 767c925 and 33921aa.

📒 Files selected for processing (12)
  • bursa.go (4 hunks)
  • bursa_test.go (2 hunks)
  • cmd/bursa/main.go (1 hunks)
  • cmd/bursa/script.go (1 hunks)
  • internal/api/api.go (5 hunks)
  • internal/api/api_test.go (1 hunks)
  • internal/cli/cli.go (2 hunks)
  • openapi.sh (1 hunks)
  • openapi/api/openapi.yaml (5 hunks)
  • openapi/api_default.go (12 hunks)
  • openapi/docs/BursaWallet.md (2 hunks)
  • openapi/model_bursa_wallet.go (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • openapi/api_default.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/api/api_test.go
  • openapi.sh
🧰 Additional context used
🧬 Code graph analysis (5)
cmd/bursa/script.go (1)
internal/cli/cli.go (4)
  • Run (31-33)
  • RunScriptCreate (125-257)
  • RunScriptValidate (287-374)
  • RunScriptAddress (376-434)
internal/cli/cli.go (2)
internal/logging/logging.go (1)
  • GetLogger (78-83)
bursa.go (16)
  • Script (68-73)
  • NewAllMultiSigScript (997-1008)
  • NewAnyMultiSigScript (1011-1022)
  • NewMultiSigScript (976-994)
  • MarshalScript (1510-1538)
  • ScriptSig (76-78)
  • ScriptAll (89-91)
  • ScriptAny (114-116)
  • ScriptNOf (139-142)
  • ScriptBefore (165-167)
  • ScriptAfter (178-180)
  • ScriptData (1502-1507)
  • UnmarshalScript (1541-1546)
  • GetScriptHash (1059-1071)
  • ValidateScript (1101-1135)
  • GetScriptAddress (1074-1096)
openapi/model_bursa_wallet.go (2)
openapi/model_bursa_key_file.go (1)
  • BursaKeyFile (22-26)
openapi/utils.go (1)
  • IsNil (335-352)
internal/api/api.go (2)
bursa.go (11)
  • Script (68-73)
  • NewMultiSigScript (976-994)
  • NewAllMultiSigScript (997-1008)
  • NewAnyMultiSigScript (1011-1022)
  • NewTimelockedScript (1025-1036)
  • MarshalScript (1510-1538)
  • UnmarshalScript (1541-1546)
  • ScriptData (1502-1507)
  • ValidateScript (1101-1135)
  • GetScriptHash (1059-1071)
  • GetScriptAddress (1074-1096)
internal/logging/logging.go (1)
  • GetLogger (78-83)
bursa.go (1)
bip32/bip32.go (2)
  • XPrv (43-43)
  • PublicKey (49-49)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: docker
  • GitHub Check: go-test (1.24.x, ubuntu-latest)
  • GitHub Check: nilaway
  • GitHub Check: Analyze (go)
🔇 Additional comments (35)
cmd/bursa/main.go (1)

42-42: LGTM!

Clean integration of the new scriptCommand() into the root command structure.

cmd/bursa/script.go (4)

22-34: LGTM!

Clean parent command setup for the script subcommand group.


36-97: LGTM!

Well-structured command with clear help text examples covering all script creation modes (N-of-M, all, any, timelocked).


99-143: LGTM!

Help text examples now correctly demonstrate the --structural-only flag for timelock-only validation, addressing the previous review concern. The panic on MarkFlagRequired failure is acceptable as this only occurs during program initialization for programming errors.


145-177: LGTM!

Clean implementation with sensible defaults and required flag enforcement.

bursa_test.go (7)

882-954: LGTM!

Comprehensive test coverage for all script types (Sig, All, Any, NOf, Before, After) with proper type assertions and CBOR encoding verification.


956-974: LGTM!

Good coverage of script hash length verification and address generation with both valid and invalid network scenarios.


976-1005: LGTM!

Thorough test of the CIP-1854 multi-sig key derivation hierarchy, validating key types at each level.


1007-1058: LGTM!

Good coverage of script validation across all script types, including timelock boundary conditions.


1060-1137: LGTM!

Comprehensive testing of multi-sig script construction including N-of-M, All, Any variants, timelocked wrappers, and edge case error handling.


1139-1284: LGTM!

Thorough testing of signature validation requirements and timelock boundary conditions with precise edge case coverage.


1286-1361: LGTM!

Good round-trip testing for script serialization with proper error handling validation for invalid script types and malformed data.

internal/cli/cli.go (5)

18-19: LGTM!

Appropriate imports added for hex encoding and JSON marshaling.


125-257: LGTM!

Well-structured implementation with comprehensive validation addressing all previous review concerns:

  • Mutual exclusivity of --timelock-before and --timelock-after (Lines 155-160)
  • Validation that required doesn't exceed key hash count (Lines 179-189)
  • Proper error handling on file close (Lines 234-239)

259-285: LGTM!

Clean recursive implementation to determine if a script tree requires signatures.


287-374: LGTM!

Good implementation that recomputes the script hash from the parsed script rather than trusting the file contents, addressing the previous review concern about hash integrity.


376-434: LGTM!

Clean implementation with proper script hash recomputation from the parsed script.

internal/api/api.go (7)

19-19: LGTM!

Required import for hex encoding of script hashes.


133-179: LGTM!

Well-structured request/response types with correct validation:

  • Required field validation fixed with omitempty to allow zero value for all/any types
  • Key hash length (56 hex chars = 28 bytes) matches Blake2b-224 output
  • Signature length (128 hex chars = 64 bytes) matches Ed25519 signature size

258-261: LGTM!

Clean route registration for the new script endpoints.


535-540: LGTM!

Safe error response handling using json.Marshal to prevent JSON injection.


909-1046: LGTM!

Well-implemented handler with:

  • Safe error responses using json.Marshal
  • Explicit mutual exclusion check for timelock_before and timelock_after (Lines 1007-1019)
  • Proper validation and error handling throughout

1059-1161: LGTM!

Clean implementation with proper script hash computation from the parsed script.


1174-1260: LGTM!

Safe implementation with json.Marshal for error responses, addressing the previous JSON injection concern.

openapi/docs/BursaWallet.md (1)

1-497: Auto-generated documentation accurately reflects model changes.

The documentation correctly describes the new committee and DRep key accessors following the established Get/GetOk/Set/Has pattern.

openapi/model_bursa_wallet.go (3)

22-41: Auto-generated struct fields follow established patterns.

The new committee and DRep key fields correctly use pointer types for optional values and follow the existing naming and tagging conventions.


60-346: Generated accessor methods are consistent and correct.

All 36 new accessor methods (Get/GetOk/Has/Set for 9 fields) follow the established pattern and use IsNil checks appropriately.


644-701: ToMap serialization correctly handles new fields.

The serialization logic properly uses IsNil checks to conditionally include committee and DRep fields, maintaining consistency with existing field handling.

openapi/api/openapi.yaml (2)

172-230: Validation constraints align with implementation limits.

The maximum value of 2147483647 (2^31-1) for ID fields correctly matches the BIP32 hardened derivation constraint enforced in the code. Name and description length limits are reasonable.


319-358: Schema structure correctly extends wallet model.

The new committee and DRep key properties follow the established pattern of referencing bursa.KeyFile, maintaining consistency with existing payment and stake key fields.

bursa.go (5)

67-189: Script types correctly implement CBOR encoding with error handling.

The CBOR methods now properly return errors, addressing previous review feedback. The decode/re-encode pattern in composite scripts (lines 98-110, 122-134, 148-160) ensures correct CBOR array structure for nested scripts.


851-942: Multi-sig key derivation correctly implements CIP-1854.

The derivation path m/1854'/1815'/account' (line 858-860) properly uses purpose 1854' for multi-signature wallets as specified in CIP-1854, distinguishing them from standard single-key wallets that use 1852'.


974-994: Multi-sig constructor properly validates threshold.

Line 980 correctly enforces required >= 1, preventing the creation of 0-of-N scripts that would always be satisfied regardless of signatures (addressing previous review feedback).


1058-1096: Script hashing and addressing implementation is correct.

Blake2b-224 (line 1064) is the correct hash function for Cardano script hashes. The panic at line 1067 is acceptable since blake2b.New only fails with invalid parameters. Script addresses correctly use AddressTypeScriptNone with no stake component.


1686-1750: JSON deserialization properly validates numeric constraints.

The validation logic correctly addresses previous review feedback:

  • Lines 1691-1693: Rejects fractional n values for nOf scripts
  • Line 1699: Enforces n >= 1 (not >= 0), matching constructor validation
  • Lines 1728-1732, 1740-1744: Rejects negative and fractional slot values for timelocks

This prevents malformed JSON from creating unexpected script semantics.

@wolf31o2 wolf31o2 force-pushed the feat/cip-1854-multisig-support branch from 33921aa to 3c87157 Compare December 9, 2025 14:35
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (9)
openapi.sh (1)

5-5: Portability issue resolved with OS detection.

The conditional check for $OSTYPE correctly handles the sed -i syntax differences between macOS (BSD) and Linux (GNU). This addresses the previously flagged portability concerns.

internal/cli/cli.go (4)

125-160: Past issues resolved: timelocks and required validation.

The validation logic correctly handles:

  • Mutual exclusivity of --timelock-before and --timelock-after (lines 155-160)
  • Mutual exclusivity of --all, --any, and --required flags
  • Minimum key hash requirement

179-189: Past issue resolved: required count validation.

The validation that required cannot exceed the number of key hashes has been correctly implemented.


234-239: Past issue resolved: file close error handling.

The deferred file close now properly checks and handles errors, addressing the concern about silent data loss.


322-328: Past issue resolved: script hash recomputation.

The script hash is now correctly recomputed from the unmarshaled script rather than trusting the file contents, addressing the previous concern about hash integrity.

internal/api/api.go (4)

133-141: Past issue resolved: validation tag fix.

The Required field validation tag now correctly uses required_if=Type nOf,omitempty,min=1, which allows zero values for all/any types while enforcing validation when Type=="nOf".


535-540: Past issue resolved: JSON injection prevention.

Error responses now use proper JSON marshaling via json.Marshal(errorResp) with a safe fallback, preventing potential JSON injection from error messages containing special characters.


1006-1024: Past issue resolved: both timelocks specified check.

The handler now correctly rejects requests that specify both timelock_before and timelock_after, addressing the previous concern about silent ignoring of timelockAfter.


1220-1233: Past issue resolved: safe JSON error response.

The error response for address generation failures now uses proper JSON marshaling with a safe fallback, preventing potential JSON injection from error messages.

🧹 Nitpick comments (2)
bursa.go (2)

1058-1071: Inconsistent error handling for Blake2b initialization.

Line 1067 panics if blake2b.New(28, nil) fails, while Line 1050 returns an error for the identical call. Although blake2b.New with valid parameters should never fail, the inconsistency is confusing.

Consider handling both cases consistently—either returning an error from GetScriptHash or documenting why panic is acceptable here (e.g., if GetScriptHash is always called in contexts where panicking is safe).


1163-1175: Structural validation mode has inconsistent signature handling.

When requireSignatures=false (structural validation) and signatures are provided:

  • Lines 1169-1171 validate that each signature is exactly 64 bytes
  • Line 1174 requires at least one signature

This is inconsistent with the "structural validation" concept. If we're only checking structure (not cryptographic validity), why enforce:

  1. Signature format (64 bytes)?
  2. Presence of signatures?

Consider either:

  • Ignoring all signature checks when requireSignatures=false (return true immediately), or
  • Documenting why format validation is performed even in structural mode
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 33921aa and 3c87157.

📒 Files selected for processing (12)
  • bursa.go (4 hunks)
  • bursa_test.go (2 hunks)
  • cmd/bursa/main.go (1 hunks)
  • cmd/bursa/script.go (1 hunks)
  • internal/api/api.go (5 hunks)
  • internal/api/api_test.go (1 hunks)
  • internal/cli/cli.go (2 hunks)
  • openapi.sh (1 hunks)
  • openapi/api/openapi.yaml (5 hunks)
  • openapi/api_default.go (12 hunks)
  • openapi/docs/BursaWallet.md (2 hunks)
  • openapi/model_bursa_wallet.go (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • internal/api/api_test.go
  • openapi/api_default.go
  • cmd/bursa/script.go
  • cmd/bursa/main.go
🧰 Additional context used
🧬 Code graph analysis (3)
openapi/model_bursa_wallet.go (2)
openapi/model_bursa_key_file.go (1)
  • BursaKeyFile (22-26)
openapi/utils.go (1)
  • IsNil (335-352)
bursa_test.go (1)
bursa.go (14)
  • NewScriptSig (945-947)
  • NewScriptAll (950-952)
  • NewScriptAny (955-957)
  • NewScriptNOf (960-962)
  • NewScriptBefore (965-967)
  • NewScriptAfter (970-972)
  • GetScriptHash (1059-1071)
  • GetScriptAddress (1074-1096)
  • ErrInvalidNetwork (47-47)
  • ScriptAll (89-91)
  • MarshalScript (1510-1538)
  • UnmarshalScript (1541-1546)
  • ScriptData (1502-1507)
  • Script (68-73)
bursa.go (1)
bip32/bip32.go (2)
  • XPrv (43-43)
  • PublicKey (49-49)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: docker
  • GitHub Check: nilaway
  • GitHub Check: go-test (1.24.x, ubuntu-latest)
  • GitHub Check: Analyze (go)
🔇 Additional comments (30)
bursa_test.go (12)

18-18: LGTM!

The crypto/ed25519 import is correctly added to support the new TestMultiSigScriptFromKeys test that generates Ed25519 key pairs.


882-954: LGTM!

Comprehensive test coverage for all six native script types. The test correctly verifies type identifiers (0-5) and CBOR encoding for each variant.


956-961: LGTM!

Correctly validates that GetScriptHash produces a 28-byte Blake2b-224 hash.


963-974: LGTM!

Properly validates mainnet address generation (Bech32 addr1 prefix) and error handling for invalid networks.


976-1005: LGTM!

Thorough test of CIP-1854 multi-signature key derivation hierarchy, validating the entire chain from root key through account, payment, and stake keys with correct type assertions.


1007-1058: LGTM!

Good coverage of script validation logic across all script types, with correct timelock boundary testing. The comments clarify the expected behavior for each case.


1060-1095: LGTM!

Comprehensive test of multi-signature script constructors and timelocked script wrapping, with appropriate type and structure validation.


1097-1112: LGTM!

Properly tests NewMultiSigScriptFromKeys by generating Ed25519 keys and validating the resulting script structure.


1114-1137: LGTM!

Good edge case coverage validating that invalid constructor parameters produce appropriate errors.


1139-1252: LGTM!

Thorough testing of signature validation with correct Ed25519 signature sizes (64 bytes) and proper handling of insufficient vs. sufficient signature counts.


1254-1284: LGTM!

Precise boundary testing confirms the exclusive comparison semantics for timelocks: Before requires slot < boundary and After requires slot > boundary.


1286-1361: LGTM!

Comprehensive round-trip testing for script serialization/deserialization, including proper error handling for invalid script types and malformed data.

internal/cli/cli.go (4)

18-19: LGTM!

The encoding/hex and encoding/json imports are correctly added to support the new script CLI operations.


259-285: LGTM!

The scriptRequiresSignatures helper correctly traverses the script tree to determine if any component requires signatures, with appropriate handling of all script types.


287-374: LGTM!

The RunScriptValidate function properly handles script validation with:

  • Script file parsing and unmarshaling
  • Hash recomputation for integrity
  • Signature parsing and validation
  • Appropriate distinction between structural and full validation modes

376-434: LGTM!

The RunScriptAddress function correctly:

  • Recomputes the script hash from the unmarshaled script
  • Generates the network-specific address
  • Outputs a consistent JSON response
internal/api/api.go (6)

19-19: LGTM!

The encoding/hex import is correctly added to support hex encoding/decoding in the script API handlers.


143-179: LGTM!

Well-structured request/response types for the script API:

  • ScriptValidateRequest properly validates signatures format (hex, 128 chars = 64 bytes)
  • ScriptAddressRequest correctly requires script and network
  • Response types align with the CLI output formats

258-261: LGTM!

Script routes are correctly registered with appropriate HTTP method handling in the handlers.


909-1046: LGTM!

The handleScriptCreate handler is well-implemented with:

  • Proper request validation using struct tags
  • Safe JSON error responses (no injection risk)
  • Additional runtime validation for nOf required count
  • Correct handling of mutually exclusive timelocks

1059-1161: LGTM!

The handleScriptValidate handler properly:

  • Unmarshals scripts and validates their structure
  • Computes the script hash from the parsed script (not trusting input)
  • Parses and validates signature format
  • Returns a consistent validation response

1174-1260: LGTM!

The handleScriptAddress handler correctly generates script addresses with proper error handling and consistent response format.

openapi/docs/BursaWallet.md (2)

7-15: LGTM!

The new BursaWallet properties for committee (cold/hot) and DRep keys are correctly documented as optional pointers to BursaKeyFile, consistent with CIP-1854 governance key support.


45-269: LGTM!

Auto-generated accessor documentation follows the established pattern with consistent Get/GetOk/Set/Has methods for all new committee and DRep key fields.

openapi/model_bursa_wallet.go (1)

1-737: LGTM! Generated code follows consistent patterns.

This file is auto-generated by OpenAPI Generator (line 10), and the new committee and DRep key fields follow the same accessor/serialization patterns as existing fields. The implementation is consistent and correct for generated code.

openapi/api/openapi.yaml (1)

172-235: LGTM! Validation constraints are appropriate.

The added constraints are reasonable:

  • Name fields: 1-100 characters prevents empty/excessively long names
  • Description: 500 character max is sufficient for descriptions
  • Numeric IDs: maximum: 2147483647 (2^31-1) aligns with signed 32-bit integer limits and the ErrInvalidDerivationIndex validation in the Go code
bursa.go (4)

67-189: LGTM! Script types are well-structured.

The Script interface and implementations are clean and follow best practices:

  • CBOR() properly returns errors (addresses previous feedback)
  • Composite scripts recursively encode sub-scripts correctly
  • Type identifiers follow Cardano native script conventions

851-942: LGTM! CIP-1854 multi-sig derivation is correctly implemented.

The multi-signature key derivation functions properly follow CIP-1854:

  • Uses path m/1854'/1815'/account' (vs m/1852'/1815'/account' for standard wallets)
  • Validates derivation indices < 2^31
  • Follows the same patterns as existing key derivation functions

1501-1546: LGTM! Script serialization is well-structured.

MarshalScript and UnmarshalScript provide clean JSON serialization with:

  • Optional address generation when network is specified
  • Consistent script hash generation
  • Proper error handling throughout

1686-1750: LGTM! Input validation properly rejects invalid values.

The mapToScript function correctly validates:

  • Lines 1691-1693: Rejects non-integral n values for nOf scripts
  • Lines 1699-1705: Enforces n is between 1 and script count
  • Lines 1728-1732, 1740-1744: Rejects negative or fractional slot values

This addresses previous review feedback and prevents edge cases.

Comment on lines 254 to 317
payment_vkey:
description: description
type: type
cborHex: cborHex
payment_extended_skey:
description: description
type: type
cborHex: cborHex
stake_address: stake_address
description: Payment Verification Key
type: PaymentVerificationKeyShelley_ed25519
cborHex: 5820a9ebe2e435c03608fbdec443686d23661a796ab6d4dea71734c69b6dde310880
payment_skey:
description: description
type: type
cborHex: cborHex
stake_vkey:
description: description
type: type
cborHex: cborHex
payment_address: payment_address
description: Payment Verification Key
type: PaymentVerificationKeyShelley_ed25519
cborHex: 5820a9ebe2e435c03608fbdec443686d23661a796ab6d4dea71734c69b6dde310880
committee_hot_extended_skey:
description: Payment Verification Key
type: PaymentVerificationKeyShelley_ed25519
cborHex: 5820a9ebe2e435c03608fbdec443686d23661a796ab6d4dea71734c69b6dde310880
committee_hot_vkey:
description: Payment Verification Key
type: PaymentVerificationKeyShelley_ed25519
cborHex: 5820a9ebe2e435c03608fbdec443686d23661a796ab6d4dea71734c69b6dde310880
stake_skey:
description: description
type: type
cborHex: cborHex
mnemonic: mnemonic
description: Payment Verification Key
type: PaymentVerificationKeyShelley_ed25519
cborHex: 5820a9ebe2e435c03608fbdec443686d23661a796ab6d4dea71734c69b6dde310880
drep_vkey:
description: Payment Verification Key
type: PaymentVerificationKeyShelley_ed25519
cborHex: 5820a9ebe2e435c03608fbdec443686d23661a796ab6d4dea71734c69b6dde310880
committee_cold_vkey:
description: Payment Verification Key
type: PaymentVerificationKeyShelley_ed25519
cborHex: 5820a9ebe2e435c03608fbdec443686d23661a796ab6d4dea71734c69b6dde310880
payment_extended_skey:
description: Payment Verification Key
type: PaymentVerificationKeyShelley_ed25519
cborHex: 5820a9ebe2e435c03608fbdec443686d23661a796ab6d4dea71734c69b6dde310880
drep_skey:
description: Payment Verification Key
type: PaymentVerificationKeyShelley_ed25519
cborHex: 5820a9ebe2e435c03608fbdec443686d23661a796ab6d4dea71734c69b6dde310880
stake_address: stake1uy9ggsc9qls4pu9qvyyacwnmr9tt0gzcdt5s0zj4au8qkqc65ge8t
committee_cold_extended_skey:
description: Payment Verification Key
type: PaymentVerificationKeyShelley_ed25519
cborHex: 5820a9ebe2e435c03608fbdec443686d23661a796ab6d4dea71734c69b6dde310880
stake_vkey:
description: Payment Verification Key
type: PaymentVerificationKeyShelley_ed25519
cborHex: 5820a9ebe2e435c03608fbdec443686d23661a796ab6d4dea71734c69b6dde310880
committee_cold_skey:
description: Payment Verification Key
type: PaymentVerificationKeyShelley_ed25519
cborHex: 5820a9ebe2e435c03608fbdec443686d23661a796ab6d4dea71734c69b6dde310880
payment_address: addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a429mg32v29q3cg4sdj5c9ts5vxknc0yutnj8x8r8qy2l2q9sgds2
committee_hot_skey:
description: Payment Verification Key
type: PaymentVerificationKeyShelley_ed25519
cborHex: 5820a9ebe2e435c03608fbdec443686d23661a796ab6d4dea71734c69b6dde310880
mnemonic: abandon abandon abandon abandon abandon abandon abandon abandon
abandon abandon abandon about
stake_extended_skey:
description: description
type: type
cborHex: cborHex
description: Payment Verification Key
type: PaymentVerificationKeyShelley_ed25519
cborHex: 5820a9ebe2e435c03608fbdec443686d23661a796ab6d4dea71734c69b6dde310880
drep_extended_skey:
description: Payment Verification Key
type: PaymentVerificationKeyShelley_ed25519
cborHex: 5820a9ebe2e435c03608fbdec443686d23661a796ab6d4dea71734c69b6dde310880
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Examples reuse identical values for different key types.

All key fields in the wallet example use the same type: PaymentVerificationKeyShelley_ed25519, description: Payment Verification Key, and cborHex value, which is unrealistic. Different keys should have distinct:

  • Type values (e.g., StakeVerificationKeyShelley_ed25519 for stake_vkey, DRepSigningKeyShelley_ed25519 for drep_skey)
  • Descriptions (e.g., Committee Hot Signing Key, DRep Verification Key)
  • cborHex values (each key has unique hex)

This may confuse API consumers trying to understand the wallet structure.

@wolf31o2 wolf31o2 force-pushed the feat/cip-1854-multisig-support branch from 3c87157 to ff07c33 Compare December 9, 2025 22:09
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
openapi/api/openapi.yaml (1)

254-317: Examples still reuse identical values for different key types.

All key fields in the wallet example continue to use the same type: PaymentVerificationKeyShelley_ed25519, description: Payment Verification Key, and cborHex value. Different keys should have distinct type values (e.g., CommitteeHotSigningKeyShelley_ed25519 for committee_hot_skey), descriptions, and cborHex values.

🧹 Nitpick comments (5)
docs/swagger.yaml (1)

26-55: Consider adding maxItems constraint to key_hashes array.

The key_hashes array in ScriptCreateRequest has minItems: 1 but no maxItems constraint. Without an upper bound, a malicious client could potentially send thousands of key hashes, causing excessive memory usage or processing time.

Consider adding a reasonable maximum (e.g., 255 signers) both in this OpenAPI spec and in the corresponding Go struct validation tag in internal/api/api.go line 137.

Based on static analysis hints.

docs/swagger.json (1)

22-64: Align script schema field naming and enums for consistency.

The three script endpoints and their models look coherent, but a few naming/details would be clearer if aligned:

  • api.ScriptCreateRequest.network / api.ScriptAddressRequest.network / api.ScriptValidateRequest.network are enums, while api.ScriptAddressResponse.network is a free-form string. Consider also declaring it as "enum": ["mainnet","testnet"] for symmetry.
  • api.ScriptResponse exposes hash, while the validate and address responses expose scriptHash. Using a single name (scriptHash or hash) across all script responses would make the API easier to consume.
  • In api.ScriptValidateRequest, signatures is an array of hex strings, but api.ScriptValidateResponse.signatures is an integer count. That’s valid, but mildly surprising; renaming the response field to something like signatureCount (or adding description text in the schema) would reduce ambiguity.

These are surface-level adjustments but easier to change now before external clients depend on them.

Also applies to: 65-107, 108-150, 362-504

internal/cli/cli.go (2)

126-213: Script creation flag validation and construction logic look solid (one small naming nit).

The mutual-exclusion checks for --all / --any / --required, the non-empty key-hash check, the required <= len(keyHashes) guard, and the explicit timelockBefore/timelockAfter conflict check are all good and prevent impossible or panicking script configurations. The selection of NewAllMultiSigScript / NewAnyMultiSigScript / NewMultiSigScript plus the optional NewTimelockedScript wrapper matches the underlying library’s constructors correctly.

Minor: any bool as a parameter name shadows Go’s predeclared any type; consider renaming it to useAny or similar to avoid confusion in call sites and tooling.


371-421: Address generation CLI is correct; consider slightly clearer network error messaging.

RunScriptAddress mirrors RunScriptValidate’s input handling, recomputes scriptHash from the unmarshaled script, and calls GetScriptAddress(script, network) so invalid/empty networks will surface as errors from the library. As a small UX improvement, you might want to include the network value in the error log when address generation fails, to make misconfigured invocations easier to diagnose.

Example:

logger.Error("failed to generate script address", "network", network, "error", err)
bursa_test.go (1)

1139-1252: Clarify that “requireSignatures” validation is structural (count/length) rather than cryptographic.

TestValidateScriptWithSignatures treats any 64-byte slice as a “valid” signature and asserts success once the expected number of 64-byte entries is present. Given that ValidateScript has no access to the transaction body or public keys, it cannot actually perform Ed25519 verification; in practice it’s enforcing structural conditions (enough signatures of the right length), not cryptographic correctness.

If that is the intended contract, consider making this explicit in documentation and/or naming (for example, documenting requireSignatures as “enforce signature presence and shape constraints only”) so API/CLI consumers don’t mistake these checks for full on-chain-style signature validation.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3c87157 and ff07c33.

📒 Files selected for processing (14)
  • bursa.go (5 hunks)
  • bursa_test.go (2 hunks)
  • cmd/bursa/main.go (1 hunks)
  • cmd/bursa/script.go (1 hunks)
  • docs/docs.go (3 hunks)
  • docs/swagger.json (3 hunks)
  • docs/swagger.yaml (2 hunks)
  • internal/api/api.go (5 hunks)
  • internal/api/api_test.go (1 hunks)
  • internal/cli/cli.go (2 hunks)
  • openapi.sh (1 hunks)
  • openapi/api/openapi.yaml (5 hunks)
  • openapi/docs/BursaWallet.md (2 hunks)
  • openapi/model_bursa_wallet.go (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • openapi/docs/BursaWallet.md
  • openapi.sh
  • internal/api/api_test.go
🧰 Additional context used
🧬 Code graph analysis (5)
internal/cli/cli.go (2)
internal/logging/logging.go (1)
  • GetLogger (78-83)
bursa.go (15)
  • Script (68-73)
  • NewAllMultiSigScript (997-1008)
  • NewAnyMultiSigScript (1011-1022)
  • NewMultiSigScript (976-994)
  • NewTimelockedScript (1025-1036)
  • MarshalScript (1502-1530)
  • ScriptSig (76-78)
  • ScriptAll (89-91)
  • ScriptAny (114-116)
  • ScriptNOf (139-142)
  • ScriptBefore (165-167)
  • ScriptAfter (178-180)
  • ScriptData (1494-1499)
  • GetScriptHash (1059-1071)
  • GetScriptAddress (1074-1096)
internal/api/api.go (2)
bursa.go (11)
  • Script (68-73)
  • NewMultiSigScript (976-994)
  • NewAllMultiSigScript (997-1008)
  • NewAnyMultiSigScript (1011-1022)
  • NewTimelockedScript (1025-1036)
  • MarshalScript (1502-1530)
  • UnmarshalScript (1533-1538)
  • ScriptData (1494-1499)
  • ValidateScript (1101-1135)
  • GetScriptHash (1059-1071)
  • GetScriptAddress (1074-1096)
internal/logging/logging.go (1)
  • GetLogger (78-83)
openapi/model_bursa_wallet.go (2)
openapi/model_bursa_key_file.go (1)
  • BursaKeyFile (22-26)
openapi/utils.go (1)
  • IsNil (335-352)
bursa.go (1)
bip32/bip32.go (2)
  • XPrv (43-43)
  • PublicKey (49-49)
bursa_test.go (1)
bursa.go (23)
  • NewScriptSig (945-947)
  • NewScriptAll (950-952)
  • NewScriptAny (955-957)
  • NewScriptNOf (960-962)
  • NewScriptBefore (965-967)
  • NewScriptAfter (970-972)
  • GetScriptHash (1059-1071)
  • GetScriptAddress (1074-1096)
  • ErrInvalidNetwork (47-47)
  • GetMultiSigAccountKey (852-862)
  • GetMultiSigPaymentKey (865-873)
  • GetMultiSigPaymentVKey (876-893)
  • GetMultiSigPaymentSKey (896-902)
  • GetMultiSigStakeKey (905-913)
  • GetMultiSigStakeVKey (916-933)
  • GetMultiSigStakeSKey (936-942)
  • ValidateScript (1101-1135)
  • NewTimelockedScript (1025-1036)
  • ScriptAll (89-91)
  • MarshalScript (1502-1530)
  • UnmarshalScript (1533-1538)
  • ScriptData (1494-1499)
  • Script (68-73)
🪛 Checkov (3.2.334)
docs/swagger.yaml

[medium] 29-34: Ensure that arrays have a maximum number of items

(CKV_OPENAPI_21)

🔇 Additional comments (10)
internal/api/api.go (2)

133-141: LGTM!

The request validation is well-structured with appropriate constraints for script creation. The Required field validation has been properly fixed to include omitempty to avoid false positives for all/any script types.


1007-1024: Good fix for timelock validation.

The code now properly rejects requests with both timelock_before and timelock_after, addressing the previous review concern about silent behavior.

bursa.go (1)

68-188: LGTM!

The Script interface and implementations are well-designed. The CBOR error handling has been properly addressed from previous reviews, with all methods now returning errors appropriately.

cmd/bursa/script.go (1)

99-143: LGTM!

The help text examples now correctly demonstrate structural validation with the --structural-only flag for timelock-only scripts (line 120), addressing the previous review concern about misleading documentation.

docs/swagger.json (1)

600-611: KeyFile schema simplification looks good.

Dropping JSON example metadata from bursa.KeyFile fields while keeping types unchanged is safe for clients and keeps the schema focused.

internal/cli/cli.go (3)

214-248: Nice improvement on output file handling.

Explicitly checking file.Close() in the deferred function ensures I/O errors on close aren’t silently ignored after a successful Encode, which is important for correctness when writing script JSON to disk.


260-280: Recursive scriptRequiresSignatures helper is correct and readable.

Using slices.ContainsFunc with a recursive predicate over ScriptAll / ScriptAny / ScriptNOf and treating ScriptBefore/ScriptAfter as non-signature-bearing cleanly captures “does any subtree require signatures?”. This matches how it’s consumed in RunScriptValidate to decide whether signatures are mandatory when --structural-only is false.


282-369: Script validate CLI behavior and output are well-structured.

RunScriptValidate correctly: reads ScriptData, unmarshals via UnmarshalScript, recomputes the hash from the parsed script (avoiding stale/forged hash fields), decodes signatures from hex, enforces presence of signatures when the script tree contains any sig nodes and --structural-only is not set, and delegates to ValidateScript with requireSignatures = !structuralOnly. The JSON result (valid/slot/signature count/scriptHash/structuralOnly) is straightforward for downstream tooling.

No changes needed from my side here.

bursa_test.go (2)

882-975: Good coverage of script constructors, hashing, address, and multisig builders.

The new tests around ScriptSig/ScriptAll/ScriptAny/ScriptNOf/ScriptBefore/ScriptAfter type IDs and CBOR, GetScriptHash length, script-address generation (including invalid network handling), multisig key derivation, high-level ValidateScript behavior, and the various multisig-constructor helpers (NewMultiSigScript, NewAllMultiSigScript, NewAnyMultiSigScript, NewTimelockedScript, NewMultiSigScriptFromKeys) collectively give strong coverage of the native-script surface. The edge-case tests for invalid parameter combinations (e.g., required out of range, empty key sets) also match the underlying constructor contracts.

No issues spotted in these tests.

Also applies to: 976-1005, 1007-1095, 1097-1137


1286-1361: Round‑trip and error‑path tests for ScriptData look correct.

TestScriptRoundTrip exercises MarshalScript/UnmarshalScript for both ScriptSig and ScriptAll, and also covers invalid Type values and bad n values for nOf scripts, ensuring deserialization fails cleanly in those cases. This gives good confidence in the JSON <-> internal script conversion layer.

@wolf31o2 wolf31o2 force-pushed the feat/cip-1854-multisig-support branch 2 times, most recently from ef8e5c3 to c8bfadf Compare December 10, 2025 13:50
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (5)
internal/api/api_test.go (1)

734-818: Good negative coverage for script validate error cases

The table‑driven tests cover malformed keyHash, missing fields, invalid network, and unsupported script type, all asserting on 400 and an error substring. This matches the expected behavior and previous review feedback about clarifying the first case name (“invalid keyHash format”).

cmd/bursa/script.go (1)

99-143: script validate CLI help now matches validation behavior

The long help correctly distinguishes full validation vs --structural-only, and the examples now include --structural-only for timelock‑only validation, aligning with RunScriptValidate’s enforcement of signatures. This addresses the earlier review concern about misleading examples.

internal/cli/cli.go (1)

126-258: RunScriptCreate validates inputs thoroughly and handles output robustly

The create path now:

  • Enforces mutual exclusivity of --all/--any/--required and ensures at least one mode is chosen.
  • Requires at least one key hash and that required <= len(keyHashes).
  • Rejects conflicting timelocks (--timelock-before and --timelock-after set together).
  • Parses key hashes via hex.DecodeString and wraps the resulting script with an optional timelock.
  • Uses MarshalScript so hash and (optional) address are recomputed, and writes JSON either to a file (with checked Close) or stdout.

This is a solid implementation and addresses previous review concerns about validation and hash trust.

openapi/api/openapi.yaml (1)

456-520: Wallet example still uses identical values for different key types.

All key fields in the wallet example use the same placeholder values (description: description, type: type, cborHex: cborHex), which doesn't demonstrate the actual distinct values each key type should have. This was previously flagged.

bursa.go (1)

916-939: Inconsistent n validation: NewScriptNOf allows n=0 but mapToScript requires n>=1.

NewScriptNOf accepts n=0 (line 918: n < 0), but mapToScript rejects it (line 1691: n < 1). A 0-of-N script is always satisfied regardless of signatures, which may not be the intended behavior. Align the validation:

 func NewScriptNOf(n int, scripts ...Script) *NativeScript {
-	if n < 0 || n > len(scripts) {
+	if n < 1 || n > len(scripts) {
 		return nil
 	}
🧹 Nitpick comments (11)
docs/swagger.yaml (1)

29-32: Consider adding maxItems constraint to key_hashes array.

While minItems: 1 ensures at least one key hash, adding a maxItems constraint would prevent potential abuse and align with practical multi-signature limits. Consider setting a reasonable maximum (e.g., 15-20) based on your use case.

       key_hashes:
         items:
           type: string
         minItems: 1
+        maxItems: 20
         type: array
internal/api/api_test.go (2)

658-691: Script create handler test is minimal but correct

The happy‑path coverage for /api/script/create is sound (status 200 and basic field presence). If you want stronger regression protection later, consider parsing the JSON into a typed ApiScriptResponse and asserting field types/shape instead of string Contains, but this is fine for now.


820-855: Script address handler test correctly asserts key response properties

The test validates status 200 and checks for address, network, scriptHash, plus an addr1 prefix, which is a reasonable smoke test for address generation. If you later expose testnet addresses, you might want an additional case asserting the testnet prefix, but current coverage is sufficient.

cmd/bursa/script.go (1)

36-97: script create CLI maps cleanly onto RunScriptCreate

Flags and examples are coherent with RunScriptCreate’s signature (required, key-hashes, all, any, timelocks, network, output). One minor polish you might consider is using 56‑hex‑char examples to match actual Cardano key hash length, but that’s cosmetic.

internal/cli/cli.go (1)

260-296: scriptRequiresSignatures is correct but can be simplified to avoid allocations

The recursive detection over NativeScriptAll/Any/NofK is logically sound, but creating intermediate []bursa.Script slices just to use slices.ContainsFunc adds avoidable allocations.

You could iterate directly over s.Scripts and recurse:

-	case *bursa.NativeScriptAll:
-		scripts := make([]bursa.Script, len(s.Scripts))
-		for i, scr := range s.Scripts {
-			scripts[i] = scr
-		}
-		if slices.ContainsFunc(scripts, scriptRequiresSignatures) {
-			return true
-		}
+	case *bursa.NativeScriptAll:
+		for _, scr := range s.Scripts {
+			if scriptRequiresSignatures(scr) {
+				return true
+			}
+		}

(and similarly for NativeScriptAny and NativeScriptNofK). Behavior stays the same with less overhead.

openapi/model_api_script_validate_response.go (1)

18-237: ApiScriptValidateResponse matches the expected JSON schema

The generated model correctly exposes optional scriptHash, signatures, slot, and valid fields with the usual accessors and MappedNullable implementation. For now int32 for slot and signatures is adequate; if you ever move the OpenAPI spec to int64 for slots, this file will naturally regenerate accordingly.

internal/api/api.go (1)

1109-1125: Consider removing signature values from error logs.

The error logging at lines 1112-1118 includes the signature string value. While this is an error path for invalid hex format, logging signature data (even malformed) could be a concern in security-sensitive contexts. Consider logging only the index or a sanitized indicator.

 			sig, err := hex.DecodeString(sigStr)
 			if err != nil {
 				logger.Error(
 					"invalid signature format",
-					"signature",
-					sigStr,
+					"index",
+					i,
 					"error",
 					err,
 				)
openapi/docs/ApiScriptResponse.md (1)

87-92: Minor doc inconsistency: GetScriptOk return type.

The documentation shows (*map[string]interface{}, bool) but the actual implementation in model_api_script_response.go returns (map[string]interface{}, bool). This is auto-generated, so regenerating the docs may fix it.

bursa.go (3)

857-872: Script constructors silently return nil on error.

NewScriptSig (and similar constructors like NewScriptAll, NewScriptAny, NewScriptNOf, NewScriptBefore, NewScriptAfter) return nil when CBOR encoding/decoding fails, with no error indication. Callers cannot distinguish between invalid input and internal failures.

Consider returning (*NativeScript, error) to allow callers to handle failures:

-func NewScriptSig(keyHash []byte) *NativeScript {
+func NewScriptSig(keyHash []byte) (*NativeScript, error) {
 	concrete := &NativeScriptPubkey{
 		Type: 0,
 		Hash: keyHash,
 	}
 	cborData, err := cbor.Encode(concrete)
 	if err != nil {
-		return nil
+		return nil, fmt.Errorf("failed to encode script sig: %w", err)
 	}
 	var script NativeScript
 	if _, err := cbor.Decode(cborData, &script); err != nil {
-		return nil
+		return nil, fmt.Errorf("failed to decode script sig: %w", err)
 	}
-	return &script
+	return &script, nil
 }

1111-1114: ValidateScript silently returns false for non-pointer script types.

If a caller passes a value type instead of *NativeScript, the function returns false without indicating why. Since all constructors return pointers, this may not be a practical issue, but consider adding documentation or returning an error for invalid inputs.


1634-1677: Consider validating non-empty scripts array for all/any types.

The mapToScript function accepts empty scripts arrays for all and any types. An all script with zero sub-scripts trivially passes (vacuous truth), while an any script with zero sub-scripts always fails. Consider adding validation:

 case "all":
 	scriptsInterface, ok := m["scripts"].([]any)
 	if !ok {
 		return nil, errors.New("missing or invalid scripts for all script")
 	}
+	if len(scriptsInterface) == 0 {
+		return nil, errors.New("all script requires at least one sub-script")
+	}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ef8e5c3 and c8bfadf.

📒 Files selected for processing (30)
  • bursa.go (5 hunks)
  • bursa_test.go (2 hunks)
  • cmd/bursa/main.go (1 hunks)
  • cmd/bursa/script.go (1 hunks)
  • docs/docs.go (3 hunks)
  • docs/swagger.json (3 hunks)
  • docs/swagger.yaml (2 hunks)
  • internal/api/api.go (5 hunks)
  • internal/api/api_test.go (1 hunks)
  • internal/cli/cli.go (2 hunks)
  • openapi.sh (1 hunks)
  • openapi/.openapi-generator/FILES (2 hunks)
  • openapi/README.md (2 hunks)
  • openapi/api/openapi.yaml (6 hunks)
  • openapi/api_default.go (14 hunks)
  • openapi/docs/ApiScriptAddressRequest.md (1 hunks)
  • openapi/docs/ApiScriptAddressResponse.md (1 hunks)
  • openapi/docs/ApiScriptCreateRequest.md (1 hunks)
  • openapi/docs/ApiScriptResponse.md (1 hunks)
  • openapi/docs/ApiScriptValidateRequest.md (1 hunks)
  • openapi/docs/ApiScriptValidateResponse.md (1 hunks)
  • openapi/docs/BursaWallet.md (2 hunks)
  • openapi/docs/DefaultAPI.md (2 hunks)
  • openapi/model_api_script_address_request.go (1 hunks)
  • openapi/model_api_script_address_response.go (1 hunks)
  • openapi/model_api_script_create_request.go (1 hunks)
  • openapi/model_api_script_response.go (1 hunks)
  • openapi/model_api_script_validate_request.go (1 hunks)
  • openapi/model_api_script_validate_response.go (1 hunks)
  • openapi/model_bursa_wallet.go (3 hunks)
✅ Files skipped from review due to trivial changes (2)
  • openapi/docs/ApiScriptCreateRequest.md
  • openapi/docs/ApiScriptAddressResponse.md
🧰 Additional context used
🧬 Code graph analysis (10)
cmd/bursa/script.go (1)
internal/cli/cli.go (4)
  • Run (32-34)
  • RunScriptCreate (126-258)
  • RunScriptValidate (298-385)
  • RunScriptAddress (387-451)
openapi/model_api_script_validate_response.go (2)
openapi/utils.go (2)
  • MappedNullable (354-356)
  • IsNil (335-352)
bursa.go (1)
  • GetScriptHash (1063-1075)
openapi/model_api_script_create_request.go (1)
openapi/utils.go (2)
  • MappedNullable (354-356)
  • IsNil (335-352)
openapi/model_api_script_address_response.go (1)
openapi/utils.go (2)
  • MappedNullable (354-356)
  • IsNil (335-352)
internal/cli/cli.go (2)
internal/logging/logging.go (1)
  • GetLogger (78-83)
bursa.go (17)
  • Script (69-69)
  • NewAnyMultiSigScript (1015-1026)
  • NewMultiSigScript (977-998)
  • NewTimelockedScript (1029-1040)
  • MarshalScript (1507-1535)
  • NativeScript (73-73)
  • NativeScriptPubkey (74-74)
  • NativeScriptAll (75-75)
  • NativeScriptAny (76-76)
  • NativeScriptNofK (77-77)
  • NativeScriptInvalidBefore (78-78)
  • NativeScriptInvalidHereafter (79-79)
  • ScriptData (1499-1504)
  • UnmarshalScript (1538-1543)
  • GetScriptHash (1063-1075)
  • ValidateScript (1105-1132)
  • GetScriptAddress (1078-1100)
internal/api/api.go (2)
bursa.go (9)
  • Script (69-69)
  • NewMultiSigScript (977-998)
  • NewAllMultiSigScript (1001-1012)
  • NewAnyMultiSigScript (1015-1026)
  • NewTimelockedScript (1029-1040)
  • MarshalScript (1507-1535)
  • UnmarshalScript (1538-1543)
  • ScriptData (1499-1504)
  • GetScriptAddress (1078-1100)
internal/logging/logging.go (1)
  • GetLogger (78-83)
openapi/model_api_script_address_request.go (2)
openapi/utils.go (1)
  • MappedNullable (354-356)
bursa.go (1)
  • Script (69-69)
openapi/model_api_script_response.go (1)
openapi/utils.go (2)
  • MappedNullable (354-356)
  • IsNil (335-352)
bursa.go (1)
bip32/bip32.go (2)
  • XPrv (43-43)
  • PublicKey (49-49)
openapi/model_bursa_wallet.go (2)
openapi/model_bursa_key_file.go (1)
  • BursaKeyFile (22-26)
openapi/utils.go (1)
  • IsNil (335-352)
🪛 Checkov (3.2.334)
docs/swagger.yaml

[medium] 29-34: Ensure that arrays have a maximum number of items

(CKV_OPENAPI_21)

🪛 LanguageTool
openapi/docs/ApiScriptValidateRequest.md

[grammar] ~52-~52: Ensure spelling is correct
Context: ...ets Network field to given value. ### GetRequireSignatures func (o *ApiScriptValidateRequest) GetRequireSignatures() bool GetRequireSignatures returns the Require...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~58-~58: Ensure spelling is correct
Context: ... if non-nil, zero value otherwise. ### GetRequireSignaturesOk func (o *ApiScriptValidateRequest) GetRequireSignaturesOk() (*bool, bool) GetRequireSignaturesOk returns a tuple w...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🔇 Additional comments (37)
openapi/model_bursa_wallet.go (1)

23-40: LGTM: Generated model expansion follows established patterns.

The new Committee and DRep key fields are properly structured as optional pointers to BursaKeyFile, consistent with existing Payment and Stake key fields. The generated accessors and serialization logic correctly handle nil values.

cmd/bursa/main.go (1)

42-42: LGTM: Script subcommand properly integrated.

The scriptCommand() registration follows the same pattern as existing commands and correctly wires up the new CLI functionality.

openapi.sh (1)

5-5: LGTM: Portability issue resolved.

The OS-specific handling for sed -i properly addresses the macOS vs Linux portability concerns raised in previous reviews. The $OSTYPE check cleanly handles both platforms without requiring backup files.

openapi/README.md (1)

82-100: LGTM: Documentation properly updated for new script endpoints.

The README correctly documents all three new script API endpoints and their associated request/response models, maintaining consistency with the existing documentation structure.

openapi/docs/DefaultAPI.md (1)

19-215: LGTM: Comprehensive API endpoint documentation.

The documentation for the three new script endpoints (address, create, validate) is thorough and follows the established format, including usage examples and complete parameter descriptions.

openapi/docs/ApiScriptValidateRequest.md (1)

1-149: LGTM: Model documentation is complete and accurate.

The documentation thoroughly covers all properties and methods for ApiScriptValidateRequest, clearly indicating required vs optional fields.

openapi/docs/ApiScriptValidateResponse.md (1)

1-134: LGTM: Response model documentation is thorough.

The documentation clearly describes all response properties and their accessor methods, maintaining consistency with the request model documentation.

docs/swagger.yaml (2)

3-100: LGTM: Well-structured API schemas for script operations.

The new schema definitions properly use enums for validation, mark required fields, and leverage additionalProperties: true for flexible script objects. The design aligns well with the multi-signature use case.


232-315: LGTM: Script endpoints are well-documented.

All three endpoints (/api/script/address, /api/script/create, /api/script/validate) have clear descriptions, proper request/response schema references, and appropriate HTTP status codes (200, 400, 500).

openapi/docs/ApiScriptAddressRequest.md (1)

1-72: ApiScriptAddressRequest doc looks consistent with generated model

Content and method signatures match the standard OpenAPI generator pattern for a request with Network and Script fields; nothing to fix here.

internal/api/api_test.go (1)

693-732: Script validate handler test verifies both transport and semantic result

This test exercises the validate handler end‑to‑end and explicitly asserts valid == true from the decoded JSON, which is exactly what you want here. No issues.

cmd/bursa/script.go (2)

22-34: script root command wiring is straightforward and correct

The script group command is minimal and correctly registers the three subcommands; no issues here.


145-177: script address CLI is correctly parameterized

The address command requires --script and passes both scriptFile and network to RunScriptAddress, with sensible defaults and examples. No functional issues.

bursa_test.go (4)

882-977: Core script type, hash, and address tests align with implementation

The tests for NewScriptSig/All/Any/NOf/Before/After, GetScriptHash (28‑byte Blake2b‑224), and GetScriptAddress (including invalid network error) closely track the library’s advertised behavior. Timelock type ids and address prefix checks (addr1) look correct for current semantics.


978-1118: Multisig key derivation and script construction have solid coverage

TestMultiSigKeyDerivation, TestMultiSigScriptGeneration, and TestMultiSigScriptFromKeys validate CIP‑1854 key derivation, type tags on key files, and the structure of N‑of‑K/All/Any/timelocked scripts. Using real ed25519 public keys for NewMultiSigScriptFromKeys is appropriate and should remain stable across runs.


1120-1293: Signature and timelock validation tests encode the intended semantics

TestScriptGenerationEdgeCases asserts proper error conditions for invalid multisig parameters. TestValidateScriptWithSignatures clearly documents the current “structural only” interpretation of requireSignatures and enforces signature count/length per script shape, while TestValidateScriptTimelockBoundaries correctly encodes < for ScriptBefore and >= for ScriptAfter in line with Cardano native script semantics. These are valuable guardrails.


1295-1374: Round‑trip and invalid‑data tests for script (un)marshalling look good

TestScriptRoundTrip covers MarshalScript/UnmarshalScript for both ScriptSig and ScriptAll, and explicitly checks failure cases for unsupported Type and bad n values in N‑of‑K. This should catch regressions in the JSON <-> internal representation mapping.

internal/cli/cli.go (2)

298-385: RunScriptValidate matches CLI semantics and recomputes script hash

The validate helper:

  • Opens and decodes ScriptData JSON, then unmarshals to a script.
  • Recomputes the hash from the script CBOR, avoiding trusting any hash in the file.
  • Decodes signatures from hex and enforces presence only when structuralOnly == false and the script tree contains pubkey requirements (scriptRequiresSignatures).
  • Delegates to bursa.ValidateScript with requireSignatures = !structuralOnly and emits a concise JSON result.

This cleanly implements the documented behavior for full vs structural validation.


387-451: RunScriptAddress correctly derives address and hash from the script

RunScriptAddress:

  • Reads ScriptData, unmarshals to the internal script.
  • Recomputes the script hash.
  • Calls GetScriptAddress with the provided network and outputs JSON containing address, network, and scriptHash.

Error handling is consistent with the rest of the CLI (logging and os.Exit(1)), and it doesn’t rely on any precomputed hash or address in the file, which is the right choice.

openapi/.openapi-generator/FILES (1)

8-29: OpenAPI generator file list correctly includes new script models/docs

The added entries for script request/response docs and models are consistent with the new OpenAPI types and endpoints; no changes needed.

openapi/model_api_script_create_request.go (1)

20-330: ApiScriptCreateRequest generation and validation logic are appropriate

The model’s fields, JSON tags (key_hashes, network, required, timelock_*, type), required‑property checks, and DisallowUnknownFields decoding all align with how the API tests build create requests. This looks correct and doesn’t need manual adjustment.

docs/docs.go (1)

1-699: LGTM - Auto-generated Swagger documentation.

This file is generated by swaggo/swag and correctly reflects the new script API endpoints and their request/response schemas.

docs/swagger.json (1)

1-677: LGTM - Auto-generated Swagger JSON specification.

This file mirrors the Go documentation template and correctly documents the new script API surface.

openapi/model_api_script_address_response.go (1)

1-199: LGTM - Auto-generated OpenAPI model.

This file follows the standard OpenAPI Generator patterns for response models with proper nullable handling and JSON serialization.

openapi/model_api_script_address_request.go (1)

1-193: LGTM - Auto-generated OpenAPI model.

This file correctly implements the request model with required field validation during JSON unmarshaling and strict parsing via DisallowUnknownFields().

internal/api/api.go (5)

133-179: LGTM - Well-structured request/response types with proper validation.

The validation tags are correctly configured:

  • Required field uses omitempty to skip min=1 validation for all/any types (addressing previous review feedback)
  • KeyHashes validates 56 hex characters (28-byte Blake2b-224 hash)
  • Signatures validates 128 hex characters (64-byte Ed25519 signature)

258-261: LGTM - Script API routes properly registered.

The three script endpoints are correctly wired to their respective handlers.


1007-1024: Good fix for timelock mutual exclusivity.

The check for both TimelockBefore and TimelockAfter being set (lines 1007-1018) correctly addresses the previous review feedback about silently ignoring one of the timelocks.


1221-1233: LGTM - Safe error response handling.

The error response at lines 1226-1231 correctly uses json.Marshal to safely encode the error message, addressing the previous review feedback about JSON injection.


535-540: LGTM - Safe JSON error encoding pattern.

The error response correctly uses json.Marshal with a fallback, ensuring error messages containing special characters don't cause JSON injection or malformed responses.

openapi/model_api_script_response.go (1)

1-235: Auto-generated OpenAPI model looks correct.

This is standard OpenAPI Generator output with proper accessor patterns, nullable wrapper, and JSON serialization. No issues identified.

bursa.go (4)

1167-1183: Signature validation is documented as placeholder; ensure tests reflect this.

The implementation checks signature count but doesn't track which signatures satisfy which sub-scripts. A single valid signature could satisfy multiple ScriptSig children. This is documented as placeholder behavior (per past review comments), but ensure integration tests clearly verify this limitation before production use.


762-855: CIP-1854 multi-sig key derivation looks correct.

The derivation path 1854'/1815'/account'/role/index follows CIP-1854 specification. Key file creation reuses existing helpers appropriately.


1062-1100: Script hashing and address generation implementation is correct.

GetScriptHash properly uses Blake2b-224 on raw script bytes, and GetScriptAddress correctly creates a script address with no stake part using AddressTypeScriptNone.


1224-1235: No issues identified. The timelock validation logic is correct and intentionally uses user-facing semantics ("before" = valid before, "after" = valid after) rather than Cardano's inverted naming convention. Test cases confirm the validation logic produces the expected behavior.

openapi/api_default.go (1)

1-683: Auto-generated code: LGTM

This file is marked as generated by OpenAPI Generator and follows the "DO NOT EDIT" directive. The new script endpoints (ApiScriptAddressPost, ApiScriptCreatePost, ApiScriptValidatePost) follow the same established patterns as the existing wallet endpoints, with consistent:

  • Request body validation
  • Content-Type/Accept header handling
  • Error response handling for 400/500 status codes
  • Response decoding

Any adjustments should be made in the source OpenAPI spec (openapi.yaml), not in this generated file.

openapi/docs/BursaWallet.md (1)

7-15: Auto-generated documentation: LGTM

The new committee and DRep key fields are properly documented as optional BursaKeyFile pointers, consistent with other key fields in the model. The documentation follows the established patterns with complete Get/GetOk/Set/Has method documentation for each new field.

@wolf31o2 wolf31o2 force-pushed the feat/cip-1854-multisig-support branch from c8bfadf to 22f1fe2 Compare December 10, 2025 19:04
@wolf31o2
Copy link
Member Author

@cubic-dev-ai review this PR

@cubic-dev-ai
Copy link

cubic-dev-ai bot commented Dec 10, 2025

@cubic-dev-ai review this PR

@wolf31o2 I've started the AI code review. It'll take a few minutes to complete.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

9 issues found across 30 files

Prompt for AI agents (all 9 issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="internal/api/api.go">

<violation number="1" location="internal/api/api.go:146">
P2: The `Network` field in `ScriptValidateRequest` is marked as required but is never used in `handleScriptValidate`. This forces API consumers to provide a value that serves no purpose. Consider either removing the `required` validation if the field is for future use, or removing the field entirely from this request struct.</violation>
</file>

<file name="docs/swagger.json">

<violation number="1" location="docs/swagger.json:422">
P2: The `timelock_after` and `timelock_before` fields should include `&quot;format&quot;: &quot;int64&quot;` like the `slot` field, since they represent blockchain slots which can be large numbers.</violation>

<violation number="2" location="docs/swagger.json:444">
P2: Inconsistent field naming: `ScriptResponse` uses `hash` while `ScriptAddressResponse` and `ScriptValidateResponse` use `scriptHash`. Consider using `scriptHash` consistently across all response schemas for better API consistency and client code generation.</violation>
</file>

<file name="bursa.go">

<violation number="1" location="bursa.go:878">
P1: Unsafe type assertion will panic if script is not a `*NativeScript`. Use checked assertion with `, ok` pattern and handle the error case.</violation>

<violation number="2" location="bursa.go:994">
P2: `NewScriptSig` can return nil on error, but this is not checked before passing to `NewScriptNOf`, which will panic when dereferencing.</violation>
</file>

<file name="internal/cli/cli.go">

<violation number="1" location="internal/cli/cli.go:248">
P2: Misleading user output: success message is logged before deferred `file.Close()`, so if close fails, users see success message but program exits with error code 1. Consider moving the success log after explicitly closing the file, or handle the close error before logging success.</violation>
</file>

<file name="docs/swagger.yaml">

<violation number="1" location="docs/swagger.yaml:41">
P1: Missing `format: int64` for `timelock_after` field. This field represents a Cardano slot number which can exceed 32-bit integer limits. The `slot` field in `ScriptValidateRequest` correctly uses `format: int64`, but this field generates as `int32`, causing potential overflow.</violation>

<violation number="2" location="docs/swagger.yaml:43">
P1: Missing `format: int64` for `timelock_before` field. Like `timelock_after`, this represents a Cardano slot number and should use 64-bit integers to avoid overflow.</violation>
</file>

<file name="openapi/api/openapi.yaml">

<violation number="1" location="openapi/api/openapi.yaml:405">
P2: Missing `minimum: 0` constraint for derivation index fields. These BIP-32 derivation path indices must be non-negative (0 to 2147483647). Without this validation, the API would accept invalid negative values.</violation>
</file>

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

// ScriptValidateRequest defines the request payload for script validation
type ScriptValidateRequest struct {
Script map[string]any `json:"script" validate:"required"`
Network string `json:"network" validate:"required,oneof=mainnet testnet"`
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The Network field in ScriptValidateRequest is marked as required but is never used in handleScriptValidate. This forces API consumers to provide a value that serves no purpose. Consider either removing the required validation if the field is for future use, or removing the field entirely from this request struct.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/api/api.go, line 146:

<comment>The `Network` field in `ScriptValidateRequest` is marked as required but is never used in `handleScriptValidate`. This forces API consumers to provide a value that serves no purpose. Consider either removing the `required` validation if the field is for future use, or removing the field entirely from this request struct.</comment>

<file context>
@@ -129,6 +130,54 @@ type WalletUpdateRequest struct {
+// ScriptValidateRequest defines the request payload for script validation
+type ScriptValidateRequest struct {
+	Script            map[string]any `json:&quot;script&quot;                       validate:&quot;required&quot;`
+	Network           string         `json:&quot;network&quot;                      validate:&quot;required,oneof=mainnet testnet&quot;`
+	Signatures        []string       `json:&quot;signatures,omitempty&quot;         validate:&quot;dive,hexadecimal,len=128&quot;`
+	Slot              uint64         `json:&quot;slot,omitempty&quot;                                                         swaggertype:&quot;integer&quot; format:&quot;int64&quot;`
</file context>
Fix with Cubic

"address": {
"type": "string"
},
"hash": {
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Inconsistent field naming: ScriptResponse uses hash while ScriptAddressResponse and ScriptValidateResponse use scriptHash. Consider using scriptHash consistently across all response schemas for better API consistency and client code generation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/swagger.json, line 444:

<comment>Inconsistent field naming: `ScriptResponse` uses `hash` while `ScriptAddressResponse` and `ScriptValidateResponse` use `scriptHash`. Consider using `scriptHash` consistently across all response schemas for better API consistency and client code generation.</comment>

<file context>
@@ -230,6 +359,151 @@
+                &quot;address&quot;: {
+                    &quot;type&quot;: &quot;string&quot;
+                },
+                &quot;hash&quot;: {
+                    &quot;type&quot;: &quot;string&quot;
+                },
</file context>
Fix with Cubic

"type": "integer",
"minimum": 1
},
"timelock_after": {
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The timelock_after and timelock_before fields should include "format": "int64" like the slot field, since they represent blockchain slots which can be large numbers.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/swagger.json, line 422:

<comment>The `timelock_after` and `timelock_before` fields should include `&quot;format&quot;: &quot;int64&quot;` like the `slot` field, since they represent blockchain slots which can be large numbers.</comment>

<file context>
@@ -230,6 +359,151 @@
+                    &quot;type&quot;: &quot;integer&quot;,
+                    &quot;minimum&quot;: 1
+                },
+                &quot;timelock_after&quot;: {
+                    &quot;type&quot;: &quot;integer&quot;
+                },
</file context>
Fix with Cubic

bursa.go Outdated

scripts := make([]Script, len(keyHashes))
for i, keyHash := range keyHashes {
scripts[i] = NewScriptSig(keyHash)
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: NewScriptSig can return nil on error, but this is not checked before passing to NewScriptNOf, which will panic when dereferencing.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At bursa.go, line 994:

<comment>`NewScriptSig` can return nil on error, but this is not checked before passing to `NewScriptNOf`, which will panic when dereferencing.</comment>

<file context>
@@ -724,6 +759,481 @@ func GetCommitteeHotExtendedSKey(committeeKey bip32.XPrv) (KeyFile, error) {
+
+	scripts := make([]Script, len(keyHashes))
+	for i, keyHash := range keyHashes {
+		scripts[i] = NewScriptSig(keyHash)
+	}
+
</file context>
Fix with Cubic

bursa.go Outdated
func NewScriptAll(scripts ...Script) *NativeScript {
nativeScripts := make([]NativeScript, len(scripts))
for i, script := range scripts {
nativeScripts[i] = *script.(*NativeScript)
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Unsafe type assertion will panic if script is not a *NativeScript. Use checked assertion with , ok pattern and handle the error case.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At bursa.go, line 878:

<comment>Unsafe type assertion will panic if script is not a `*NativeScript`. Use checked assertion with `, ok` pattern and handle the error case.</comment>

<file context>
@@ -724,6 +759,481 @@ func GetCommitteeHotExtendedSKey(committeeKey bip32.XPrv) (KeyFile, error) {
+func NewScriptAll(scripts ...Script) *NativeScript {
+	nativeScripts := make([]NativeScript, len(scripts))
+	for i, script := range scripts {
+		nativeScripts[i] = *script.(*NativeScript)
+	}
+	concrete := &amp;NativeScriptAll{
</file context>
Fix with Cubic

logger.Error("failed to write script to file", "error", err)
os.Exit(1)
}
logger.Info("script written to file", "file", output)
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Misleading user output: success message is logged before deferred file.Close(), so if close fails, users see success message but program exits with error code 1. Consider moving the success log after explicitly closing the file, or handle the close error before logging success.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/cli/cli.go, line 248:

<comment>Misleading user output: success message is logged before deferred `file.Close()`, so if close fails, users see success message but program exits with error code 1. Consider moving the success log after explicitly closing the file, or handle the close error before logging success.</comment>

<file context>
@@ -119,3 +122,330 @@ func RunLoad(dir string, showSecrets bool) {
+			logger.Error(&quot;failed to write script to file&quot;, &quot;error&quot;, err)
+			os.Exit(1)
+		}
+		logger.Info(&quot;script written to file&quot;, &quot;file&quot;, output)
+	} else {
+		// Print to stdout
</file context>
Fix with Cubic

Comment on lines 41 to 43
timelock_after:
type: integer
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Missing format: int64 for timelock_after field. This field represents a Cardano slot number which can exceed 32-bit integer limits. The slot field in ScriptValidateRequest correctly uses format: int64, but this field generates as int32, causing potential overflow.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/swagger.yaml, line 41:

<comment>Missing `format: int64` for `timelock_after` field. This field represents a Cardano slot number which can exceed 32-bit integer limits. The `slot` field in `ScriptValidateRequest` correctly uses `format: int64`, but this field generates as `int32`, causing potential overflow.</comment>

<file context>
@@ -1,5 +1,105 @@
+      required:
+        minimum: 1
+        type: integer
+      timelock_after:
+        type: integer
+      timelock_before:
</file context>
Suggested change
timelock_after:
type: integer
timelock_after:
format: int64
type: integer
Fix with Cubic

Comment on lines 43 to 46
timelock_before:
type: integer
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Missing format: int64 for timelock_before field. Like timelock_after, this represents a Cardano slot number and should use 64-bit integers to avoid overflow.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/swagger.yaml, line 43:

<comment>Missing `format: int64` for `timelock_before` field. Like `timelock_after`, this represents a Cardano slot number and should use 64-bit integers to avoid overflow.</comment>

<file context>
@@ -1,5 +1,105 @@
+        type: integer
+      timelock_after:
+        type: integer
+      timelock_before:
+        type: integer
+      type:
</file context>
Suggested change
timelock_before:
type: integer
timelock_before:
format: int64
type: integer
Fix with Cubic

api.WalletRestoreRequest:
properties:
account_id:
maximum: 2147483647
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Missing minimum: 0 constraint for derivation index fields. These BIP-32 derivation path indices must be non-negative (0 to 2147483647). Without this validation, the API would accept invalid negative values.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At openapi/api/openapi.yaml, line 405:

<comment>Missing `minimum: 0` constraint for derivation index fields. These BIP-32 derivation path indices must be non-negative (0 to 2147483647). Without this validation, the API would accept invalid negative values.</comment>

<file context>
@@ -187,31 +402,42 @@ components:
     api.WalletRestoreRequest:
       properties:
         account_id:
+          maximum: 2147483647
           type: integer
         address_id:
</file context>
Suggested change
maximum: 2147483647
maximum: 2147483647
minimum: 0
Fix with Cubic

@wolf31o2 wolf31o2 force-pushed the feat/cip-1854-multisig-support branch from 22f1fe2 to 846aa04 Compare December 10, 2025 21:03
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
openapi/api/openapi.yaml (1)

16-381: /api/script/create spec vs implementation mismatch (scriptHash vs hash).

The schema for api.ScriptResponse and the /api/script/create 200 response documents a scriptHash property, but the handler currently serializes bursa.ScriptData with a hash field instead. This breaks generated clients that rely on the documented scriptHash.

Either:

  • Change the handler to return ScriptResponse with scriptHash populated (preferred, see suggested diff in internal/api/api.go comment), or
  • Update this schema and related docs to use hash consistently, then regenerate the OpenAPI models/clients.

Until they’re aligned, the API contract is misleading.

♻️ Duplicate comments (5)
internal/api/api.go (3)

529-549: WalletRestore error JSON is now safely encoded.

Wrapping err.Error() in a map[string]string and marshaling via encoding/json removes the prior JSON injection risk while still returning useful messages to clients.


1006-1024: Timelock mutual-exclusion behavior is explicit and client-visible.

Rejecting requests that specify both timelock_before and timelock_after with a clear 400 error avoids the earlier “silent ignore” behavior and is reasonable for v1 of the API.


143-150: ScriptValidateRequest.Network is required but unused.

The Network field is validated and required but never referenced in handleScriptValidate. This forces clients to supply a value that has no effect; consider either using it (if you plan network-specific validation) or relaxing/removing the required constraint.

openapi/model_api_script_validate_request.go (1)

23-303: ApiScriptValidateRequest model matches the updated spec and handler usage.

Required/optional fields, Slot as *int64, strict UnmarshalJSON, and the nullable wrapper all look correct for clients targeting /api/script/validate.

openapi/api/openapi.yaml (1)

405-434: Derivation index fields still lack minimum: 0.

account_id, address_id, committee_cold_id, committee_hot_id, drep_id, payment_id, and stake_id now have maximum: 2147483647 but no minimum. For BIP-32-style indices these should be constrained to 0..2147483647; adding minimum: 0 would prevent negative values from reaching the server.

🧹 Nitpick comments (6)
docs/swagger.yaml (1)

28-32: Consider adding maxItems constraint to key_hashes array.

The key_hashes array has minItems: 1 but no upper bound. Without a maximum limit, clients could send arbitrarily large arrays, potentially causing memory exhaustion or degraded performance.

Apply this diff to add a reasonable maximum:

       key_hashes:
         items:
           type: string
         minItems: 1
+        maxItems: 100
         type: array

Adjust the maxItems value based on your expected use case and system constraints.

internal/cli/cli.go (1)

260-296: Reduce duplication in scriptRequiresSignatures.

The All, Any, and NofK cases duplicate the same script-conversion and recursion pattern. Consider extracting this logic:

+func checkNestedScripts(scripts []bursa.Script) bool {
+	wrappedScripts := make([]bursa.Script, len(scripts))
+	for i, scr := range scripts {
+		wrappedScripts[i] = scr
+	}
+	return slices.ContainsFunc(wrappedScripts, scriptRequiresSignatures)
+}
+
 func scriptRequiresSignatures(script bursa.Script) bool {
 	nativeScript, ok := script.(*bursa.NativeScript)
 	if !ok {
 		return false
 	}
 	switch s := nativeScript.Item().(type) {
 	case *bursa.NativeScriptPubkey:
 		return true
 	case *bursa.NativeScriptAll:
-		scripts := make([]bursa.Script, len(s.Scripts))
-		for i, scr := range s.Scripts {
-			scripts[i] = scr
-		}
-		if slices.ContainsFunc(scripts, scriptRequiresSignatures) {
-			return true
-		}
+		return checkNestedScripts(s.Scripts)
 	case *bursa.NativeScriptAny:
-		scripts := make([]bursa.Script, len(s.Scripts))
-		for i, scr := range s.Scripts {
-			scripts[i] = scr
-		}
-		if slices.ContainsFunc(scripts, scriptRequiresSignatures) {
-			return true
-		}
+		return checkNestedScripts(s.Scripts)
 	case *bursa.NativeScriptNofK:
-		scripts := make([]bursa.Script, len(s.Scripts))
-		for i, scr := range s.Scripts {
-			scripts[i] = scr
-		}
-		if slices.ContainsFunc(scripts, scriptRequiresSignatures) {
-			return true
-		}
+		return checkNestedScripts(s.Scripts)
 	case *bursa.NativeScriptInvalidBefore, *bursa.NativeScriptInvalidHereafter:
 		return false
 	}
 	return false
 }
internal/api/api.go (2)

1048-1161: Script validation handler is structurally sound but ignores network.

Unmarshaling via bursa.UnmarshalScript, hex-decoding signatures, and delegating to bursa.ValidateScript looks correct, and errors are mapped to JSON safely. However, as noted earlier, req.Network is never used; if network is irrelevant here, you may want to drop it from the request model to reduce noise.


1163-1260: Address generation handler behavior and error handling look good overall.

handleScriptAddress cleanly validates input, unmarshals the script, calls GetScriptAddress and GetScriptHash, and returns a well-shaped ScriptAddressResponse. Errors are JSON-encoded via maps, avoiding string interpolation issues. If you want to refine status codes further, you could distinguish ErrInvalidNetwork (400) from unexpected internal failures (500) when GetScriptAddress returns an error.

openapi/api/openapi.yaml (1)

262-381: Script script property may be overspecified as additionalProperties: { type: object }.

For api.ScriptAddressRequest, api.ScriptResponse, and api.ScriptValidateRequest, script is modeled as an object whose values must themselves be JSON objects. If your actual script JSON (from MarshalScript / UnmarshalScript) allows scalars or arrays at some positions, you may want to relax this to plain additionalProperties: {} (or a looser schema) and regenerate clients, to avoid type mismatches.

openapi/docs/BursaWallet.md (1)

7-15: New committee/DRep key fields are documented consistently

The additional Committee* and Drep* key fields are correctly listed as optional pointers to BursaKeyFile, matching the underlying model and existing payment/stake key documentation style.

If you want to improve consumer understanding, consider adding short descriptions for each of these fields in the OpenAPI spec so that this table includes more than empty description cells.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c8bfadf and 846aa04.

📒 Files selected for processing (30)
  • bursa.go (5 hunks)
  • bursa_test.go (2 hunks)
  • cmd/bursa/main.go (1 hunks)
  • cmd/bursa/script.go (1 hunks)
  • docs/docs.go (3 hunks)
  • docs/swagger.json (3 hunks)
  • docs/swagger.yaml (2 hunks)
  • internal/api/api.go (5 hunks)
  • internal/api/api_test.go (1 hunks)
  • internal/cli/cli.go (2 hunks)
  • openapi.sh (1 hunks)
  • openapi/.openapi-generator/FILES (2 hunks)
  • openapi/README.md (2 hunks)
  • openapi/api/openapi.yaml (6 hunks)
  • openapi/api_default.go (14 hunks)
  • openapi/docs/ApiScriptAddressRequest.md (1 hunks)
  • openapi/docs/ApiScriptAddressResponse.md (1 hunks)
  • openapi/docs/ApiScriptCreateRequest.md (1 hunks)
  • openapi/docs/ApiScriptResponse.md (1 hunks)
  • openapi/docs/ApiScriptValidateRequest.md (1 hunks)
  • openapi/docs/ApiScriptValidateResponse.md (1 hunks)
  • openapi/docs/BursaWallet.md (2 hunks)
  • openapi/docs/DefaultAPI.md (2 hunks)
  • openapi/model_api_script_address_request.go (1 hunks)
  • openapi/model_api_script_address_response.go (1 hunks)
  • openapi/model_api_script_create_request.go (1 hunks)
  • openapi/model_api_script_response.go (1 hunks)
  • openapi/model_api_script_validate_request.go (1 hunks)
  • openapi/model_api_script_validate_response.go (1 hunks)
  • openapi/model_bursa_wallet.go (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • cmd/bursa/script.go
  • openapi/docs/ApiScriptAddressResponse.md
  • openapi/model_api_script_address_response.go
  • openapi/README.md
🧰 Additional context used
🧬 Code graph analysis (9)
bursa_test.go (1)
bursa.go (21)
  • NewScriptSig (858-872)
  • NewScriptAll (875-897)
  • NewScriptAny (900-922)
  • NewScriptNOf (925-951)
  • NewScriptBefore (954-968)
  • NewScriptAfter (971-985)
  • GetScriptHash (1087-1099)
  • GetScriptAddress (1102-1124)
  • ErrInvalidNetwork (47-47)
  • NewMultiSigScript (989-1014)
  • NativeScriptNofK (77-77)
  • NewAllMultiSigScript (1017-1032)
  • NativeScriptAll (75-75)
  • NewAnyMultiSigScript (1035-1050)
  • NewTimelockedScript (1053-1064)
  • NativeScript (73-73)
  • NewMultiSigScriptFromKeys (1068-1084)
  • MarshalScript (1531-1559)
  • UnmarshalScript (1562-1567)
  • ScriptData (1523-1528)
  • Script (69-69)
openapi/model_api_script_validate_response.go (1)
openapi/utils.go (2)
  • MappedNullable (354-356)
  • IsNil (335-352)
openapi/model_api_script_address_request.go (2)
openapi/utils.go (1)
  • MappedNullable (354-356)
bursa.go (1)
  • Script (69-69)
internal/api/api.go (2)
bursa.go (10)
  • Script (69-69)
  • NewMultiSigScript (989-1014)
  • NewAllMultiSigScript (1017-1032)
  • NewAnyMultiSigScript (1035-1050)
  • NewTimelockedScript (1053-1064)
  • MarshalScript (1531-1559)
  • UnmarshalScript (1562-1567)
  • ScriptData (1523-1528)
  • GetScriptHash (1087-1099)
  • GetScriptAddress (1102-1124)
internal/logging/logging.go (1)
  • GetLogger (78-83)
openapi/model_api_script_response.go (1)
openapi/utils.go (2)
  • MappedNullable (354-356)
  • IsNil (335-352)
openapi/model_api_script_create_request.go (1)
openapi/utils.go (2)
  • MappedNullable (354-356)
  • IsNil (335-352)
openapi/model_api_script_validate_request.go (2)
openapi/utils.go (2)
  • MappedNullable (354-356)
  • IsNil (335-352)
bursa.go (1)
  • Script (69-69)
openapi/model_bursa_wallet.go (2)
openapi/model_bursa_key_file.go (1)
  • BursaKeyFile (22-26)
openapi/utils.go (1)
  • IsNil (335-352)
openapi/api_default.go (7)
openapi/model_api_script_address_response.go (1)
  • ApiScriptAddressResponse (22-26)
openapi/model_api_script_response.go (1)
  • ApiScriptResponse (22-27)
openapi/model_api_script_validate_response.go (1)
  • ApiScriptValidateResponse (22-27)
openapi/model_api_script_address_request.go (1)
  • ApiScriptAddressRequest (24-27)
openapi/client.go (1)
  • GenericOpenAPIError (655-659)
openapi/model_api_script_create_request.go (1)
  • ApiScriptCreateRequest (24-31)
openapi/model_api_script_validate_request.go (1)
  • ApiScriptValidateRequest (24-30)
🪛 Checkov (3.2.334)
docs/swagger.yaml

[medium] 29-34: Ensure that arrays have a maximum number of items

(CKV_OPENAPI_21)

🪛 LanguageTool
openapi/docs/ApiScriptValidateRequest.md

[grammar] ~52-~52: Ensure spelling is correct
Context: ...ets Network field to given value. ### GetRequireSignatures func (o *ApiScriptValidateRequest) GetRequireSignatures() bool GetRequireSignatures returns the Require...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~58-~58: Ensure spelling is correct
Context: ... if non-nil, zero value otherwise. ### GetRequireSignaturesOk func (o *ApiScriptValidateRequest) GetRequireSignaturesOk() (*bool, bool) GetRequireSignaturesOk returns a tuple w...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🪛 markdownlint-cli2 (0.18.1)
openapi/docs/ApiScriptCreateRequest.md

7-7: Table pipe style
Expected: no_leading_or_trailing; Actual: trailing_only; Unexpected trailing pipe

(MD055, table-pipe-style)


7-7: Table column count
Expected: 4; Actual: 3; Too few cells, row will be missing data

(MD056, table-column-count)


8-8: Table pipe style
Expected: no_leading_or_trailing; Actual: trailing_only; Unexpected trailing pipe

(MD055, table-pipe-style)


8-8: Table column count
Expected: 4; Actual: 3; Too few cells, row will be missing data

(MD056, table-column-count)


12-12: Table pipe style
Expected: no_leading_or_trailing; Actual: trailing_only; Unexpected trailing pipe

(MD055, table-pipe-style)


12-12: Table column count
Expected: 4; Actual: 3; Too few cells, row will be missing data

(MD056, table-column-count)

openapi/docs/ApiScriptValidateRequest.md

7-7: Table pipe style
Expected: no_leading_or_trailing; Actual: trailing_only; Unexpected trailing pipe

(MD055, table-pipe-style)


7-7: Table column count
Expected: 4; Actual: 3; Too few cells, row will be missing data

(MD056, table-column-count)


9-9: Table pipe style
Expected: no_leading_or_trailing; Actual: trailing_only; Unexpected trailing pipe

(MD055, table-pipe-style)


9-9: Table column count
Expected: 4; Actual: 3; Too few cells, row will be missing data

(MD056, table-column-count)

openapi/docs/DefaultAPI.md

33-33: Hard tabs
Column: 1

(MD010, no-hard-tabs)


34-34: Hard tabs
Column: 1

(MD010, no-hard-tabs)


35-35: Hard tabs
Column: 1

(MD010, no-hard-tabs)


36-36: Hard tabs
Column: 1

(MD010, no-hard-tabs)


40-40: Hard tabs
Column: 1

(MD010, no-hard-tabs)


42-42: Hard tabs
Column: 1

(MD010, no-hard-tabs)


43-43: Hard tabs
Column: 1

(MD010, no-hard-tabs)


44-44: Hard tabs
Column: 1

(MD010, no-hard-tabs)


45-45: Hard tabs
Column: 1

(MD010, no-hard-tabs)


46-46: Hard tabs
Column: 1

(MD010, no-hard-tabs)


47-47: Hard tabs
Column: 1

(MD010, no-hard-tabs)


48-48: Hard tabs
Column: 1

(MD010, no-hard-tabs)


49-49: Hard tabs
Column: 1

(MD010, no-hard-tabs)


50-50: Hard tabs
Column: 1

(MD010, no-hard-tabs)


65-65: Table pipe style
Expected: no_leading_or_trailing; Actual: trailing_only; Unexpected trailing pipe

(MD055, table-pipe-style)


65-65: Table column count
Expected: 4; Actual: 3; Too few cells, row will be missing data

(MD056, table-column-count)


80-80: No empty links

(MD042, no-empty-links)


99-99: Hard tabs
Column: 1

(MD010, no-hard-tabs)


100-100: Hard tabs
Column: 1

(MD010, no-hard-tabs)


101-101: Hard tabs
Column: 1

(MD010, no-hard-tabs)


102-102: Hard tabs
Column: 1

(MD010, no-hard-tabs)


106-106: Hard tabs
Column: 1

(MD010, no-hard-tabs)


108-108: Hard tabs
Column: 1

(MD010, no-hard-tabs)


109-109: Hard tabs
Column: 1

(MD010, no-hard-tabs)


110-110: Hard tabs
Column: 1

(MD010, no-hard-tabs)


111-111: Hard tabs
Column: 1

(MD010, no-hard-tabs)


112-112: Hard tabs
Column: 1

(MD010, no-hard-tabs)


113-113: Hard tabs
Column: 1

(MD010, no-hard-tabs)


114-114: Hard tabs
Column: 1

(MD010, no-hard-tabs)


115-115: Hard tabs
Column: 1

(MD010, no-hard-tabs)


116-116: Hard tabs
Column: 1

(MD010, no-hard-tabs)


131-131: Table pipe style
Expected: no_leading_or_trailing; Actual: trailing_only; Unexpected trailing pipe

(MD055, table-pipe-style)


131-131: Table column count
Expected: 4; Actual: 3; Too few cells, row will be missing data

(MD056, table-column-count)


146-146: No empty links

(MD042, no-empty-links)


165-165: Hard tabs
Column: 1

(MD010, no-hard-tabs)


166-166: Hard tabs
Column: 1

(MD010, no-hard-tabs)


167-167: Hard tabs
Column: 1

(MD010, no-hard-tabs)


168-168: Hard tabs
Column: 1

(MD010, no-hard-tabs)


172-172: Hard tabs
Column: 1

(MD010, no-hard-tabs)


174-174: Hard tabs
Column: 1

(MD010, no-hard-tabs)


175-175: Hard tabs
Column: 1

(MD010, no-hard-tabs)


176-176: Hard tabs
Column: 1

(MD010, no-hard-tabs)


177-177: Hard tabs
Column: 1

(MD010, no-hard-tabs)


178-178: Hard tabs
Column: 1

(MD010, no-hard-tabs)


179-179: Hard tabs
Column: 1

(MD010, no-hard-tabs)


180-180: Hard tabs
Column: 1

(MD010, no-hard-tabs)


181-181: Hard tabs
Column: 1

(MD010, no-hard-tabs)


182-182: Hard tabs
Column: 1

(MD010, no-hard-tabs)


197-197: Table pipe style
Expected: no_leading_or_trailing; Actual: trailing_only; Unexpected trailing pipe

(MD055, table-pipe-style)


197-197: Table column count
Expected: 4; Actual: 3; Too few cells, row will be missing data

(MD056, table-column-count)


212-212: No empty links

(MD042, no-empty-links)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: nilaway
  • GitHub Check: docker
  • GitHub Check: go-test (1.24.x, ubuntu-latest)
  • GitHub Check: lint
  • GitHub Check: Analyze (go)
🔇 Additional comments (31)
openapi.sh (1)

5-5: LGTM! Portable sed implementation.

The OS detection properly handles the sed -i portability issue between macOS and Linux that was flagged in previous reviews.

bursa.go (1)

1126-1156: Validation implementation is well-documented as structural-only.

The ValidateScript function and supporting documentation clearly indicate this is structural/format validation only, not cryptographic verification. The comments (lines 1158-1166) appropriately warn about the placeholder nature and include a TODO for Ed25519 verification.

This addresses previous concerns about misleading documentation. The implementation is appropriate for testing script structure logic.

bursa_test.go (1)

882-1406: LGTM! Comprehensive test coverage for script functionality.

The test suite thoroughly exercises:

  • Script type construction and validation
  • Hash and address generation
  • Multi-sig key derivation (CIP-1854)
  • Validation with various signature scenarios
  • Edge cases and error conditions
  • Round-trip serialization

The use of ed25519.GenerateKey(nil) for random keys in tests (line 1122) is appropriate for these functional tests.

cmd/bursa/main.go (1)

42-42: LGTM! Clean CLI integration.

The scriptCommand() addition follows the established pattern for CLI subcommands.

openapi/docs/ApiScriptCreateRequest.md (1)

1-171: Auto-generated API documentation looks correct.

This documents the new ApiScriptCreateRequest model with appropriate properties and methods. The markdown formatting warnings from linters are cosmetic issues in generated code.

openapi/docs/ApiScriptValidateResponse.md (1)

1-134: Auto-generated API documentation looks correct.

This documents the new ApiScriptValidateResponse model with appropriate optional fields and accessors.

openapi/docs/ApiScriptValidateRequest.md (1)

1-150: Auto-generated API documentation looks correct.

This documents the new ApiScriptValidateRequest model with the script, signatures, slot, and requireSignatures fields appropriately documented.

openapi/docs/DefaultAPI.md (1)

7-215: New API endpoints properly documented.

The three new script endpoints (ApiScriptAddressPost, ApiScriptCreatePost, ApiScriptValidatePost) are well-documented with examples, parameters, return types, and proper API structure.

internal/api/api_test.go (1)

658-855: LGTM! Comprehensive test coverage for script endpoints.

The test suite covers both happy paths and error cases well:

  • TestHandleScriptCreate validates successful script creation
  • TestHandleScriptValidate verifies validation logic including the critical valid field
  • TestHandleScriptValidateErrors uses table-driven tests for multiple error scenarios
  • TestHandleScriptAddress checks address generation

The tests appropriately verify response status codes and JSON field presence.

internal/cli/cli.go (3)

126-258: LGTM! Validation and error handling are robust.

The function properly validates:

  • Mutually exclusive flags (--all, --any, --required)
  • Timelock conflicts (can't specify both before and after)
  • Required count doesn't exceed key hashes
  • Hex decoding of key hashes

File close errors are now handled explicitly, addressing previous concerns.


298-385: LGTM! Script validation logic is sound.

The function correctly:

  • Unmarshals the script from file
  • Recomputes the script hash (addressing previous concerns about trusting file contents)
  • Validates signature requirements
  • Returns comprehensive validation results

387-451: LGTM! Address generation is correctly implemented.

The function properly:

  • Unmarshals the script
  • Recomputes the script hash for accuracy
  • Generates the network-appropriate address
openapi/.openapi-generator/FILES (1)

1-37: Auto-generated file listing.

This file tracks OpenAPI generator artifacts and requires no manual review.

openapi/docs/ApiScriptAddressRequest.md (1)

1-72: Auto-generated API documentation.

This documentation file is generated by OpenAPI Generator and requires no manual review.

docs/swagger.json (1)

22-508: LGTM! Script API definitions are well-structured.

The new script endpoints and definitions are properly specified:

  • Consistent field naming (scriptHash across all responses)
  • Appropriate data types (int64 for slot/timelock fields)
  • Standard request/response patterns
  • Proper validation constraints (minItems, enums)

Previous concerns about type formats and naming consistency have been addressed.

openapi/model_api_script_create_request.go (1)

1-330: LGTM! Well-generated OpenAPI model.

The generated code correctly implements:

  • Proper type mappings (int32 for Required, int64 for timelock fields)
  • Comprehensive accessor methods with Has* checks for optional fields
  • Strict JSON unmarshaling with DisallowUnknownFields()
  • Nullable wrapper pattern for optional usage
openapi/model_api_script_validate_response.go (1)

1-237: LGTM! Properly generated response model.

The generated code correctly implements:

  • Pointer fields with omitempty for optional response data
  • Full accessor suite with Has* methods
  • Appropriate type mappings (int32 for Signatures, int64 for Slot)
  • Nullable wrapper for flexible usage
openapi/model_api_script_address_request.go (1)

23-157: ApiScriptAddressRequest model looks consistent and robust.

Required-field checks plus DisallowUnknownFields and the nullable wrapper are all in line with the rest of the generated models; no issues from a client/SDK perspective.

internal/api/api.go (3)

133-141: ScriptCreateRequest validation and shape look coherent.

Type/Required/KeyHashes/Network validation matches the documented script-creation semantics and should guard most bad inputs before business logic.


152-179: ScriptAddressRequest/Response structs align with handler behavior.

ScriptAddressRequest matches the handler’s expectations, and ScriptAddressResponse (Address/Network/ScriptHash) matches what handleScriptAddress actually serializes.


258-262: Script routes are correctly registered on the main mux.

New /api/script/create, /api/script/validate, and /api/script/address handlers are wired into Start alongside existing wallet routes; no issues here.

docs/docs.go (1)

26-512: Swagger docs correctly describe the new script endpoints.

The added /api/script/* paths and associated api.Script* definitions line up with the new handlers and OpenAPI schemas; nothing problematic from a documentation perspective.

openapi/docs/ApiScriptResponse.md (1)

1-134: ApiScriptResponse docs are in sync with the model.

The documented fields and helper methods match the generated ApiScriptResponse type and its JSON shape; no changes needed.

openapi/model_api_script_response.go (1)

21-235: ApiScriptResponse implementation is consistent with other generated models.

Getters/setters, ToMap, custom MarshalJSON, and the nullable wrapper all follow the standard pattern and correctly honor optional fields.

openapi/model_bursa_wallet.go (1)

21-701: BursaWallet extensions for committee/DRep keys look correct.

New committee_* and drep_* key fields, their accessors, and ToMap wiring are consistent with existing payment/stake fields and the updated OpenAPI schema.

openapi/api_default.go (5)

24-41: New script methods correctly extend DefaultAPI interface

The three new Script* methods on DefaultAPI (ScriptAddressPost, ScriptCreatePost, ScriptValidatePost) follow the same builder/execute pattern as the existing wallet methods and expose appropriate response types. The context plumbing and return signatures look consistent with the rest of the interface.


184-349: ApiScriptAddressPost client implementation looks correct and consistent

  • Uses POST to /api/script/address with application/json body, which aligns with the new ApiScriptAddressRequest model.
  • Validates that r.request is non‑nil before issuing the call, returning a clear reportError otherwise.
  • Error handling for 400/500 and success decoding into *ApiScriptAddressResponse matches existing wallet endpoint patterns, including buffering and restoring the response body.

No issues from a correctness or client‑side behavior perspective.


351-516: ApiScriptCreatePost client implementation is structurally sound

  • Correctly targets POST /api/script/create using JSON and enforces a non‑nil ApiScriptCreateRequest.
  • Error and decode paths mirror ApiWallet* methods, including special handling for HTTP 400 and 500 into a string model.
  • The Request builder and Execute helper on DefaultAPIApiScriptCreatePostRequest follow the same value‑receiver pattern used elsewhere in this file.

Looks good and aligned with the generated client conventions.


518-683: ApiScriptValidatePost client implementation is correct and in line with existing endpoints

  • Uses POST /api/script/validate with the ApiScriptValidateRequest as JSON body and validates r.request != nil.
  • HTTP method, header selection (Content-Type/Accept = application/json), and error decoding for 400/500 follow the exact pattern of other generated methods.
  • Decodes into *ApiScriptValidateResponse and preserves the raw body via io.NopCloser, consistent with the rest of the client.

No behavioral or structural issues spotted.


88-90: Doc comment tweaks for @return tags are benign

The small formatting changes to // @return ... comments on existing wallet methods are purely cosmetic and keep documentation consistent; they don’t affect behavior.

Also applies to: 106-108, 122-124, 138-140, 156-158, 174-176, 711-715, 842-845, 1009-1012, 1167-1170, 1298-1301, 1465-1468

openapi/docs/BursaWallet.md (1)

45-268: Accessor documentation for new key fields aligns with existing BursaWallet patterns

For each new committee/DRep key field, the generated Get*, Get*Ok, Set*, and Has* method docs follow the exact same structure and semantics as the existing payment/stake key accessors. This keeps the public API predictable for consumers working with the richer wallet model.

No issues from a documentation/API‑surface standpoint.

Comment on lines +874 to +897
// NewScriptAll creates an "all" script requiring all sub-scripts to be satisfied
func NewScriptAll(scripts ...Script) *NativeScript {
nativeScripts := make([]NativeScript, len(scripts))
for i, script := range scripts {
nativeScript, ok := script.(*NativeScript)
if !ok {
return nil
}
nativeScripts[i] = *nativeScript
}
concrete := &NativeScriptAll{
Type: 1,
Scripts: nativeScripts,
}
cborData, err := cbor.Encode(concrete)
if err != nil {
return nil
}
var script NativeScript
if _, err := cbor.Decode(cborData, &script); err != nil {
return nil
}
return &script
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Inconsistent error handling: CBOR encoding errors are silently ignored.

NewScriptAll (and similarly NewScriptAny, NewScriptNOf, NewScriptBefore, NewScriptAfter) performs CBOR encoding that can fail, but returns nil without an error value. This creates two issues:

  1. Callers cannot distinguish between success-with-nil-result and encoding-failure
  2. Higher-level functions like NewMultiSigScript (line 1013) return (nil, nil) when these functions fail, masking the error

Consider either:

  • Option 1: Change these functions to return (*NativeScript, error) for consistency with NewScriptSig
  • Option 2: Make these functions panic on CBOR encoding errors (since encoding failure represents a programming error with the internal structures)

Apply this diff for Option 1 (NewScriptAll example; repeat for other functions):

-func NewScriptAll(scripts ...Script) *NativeScript {
+func NewScriptAll(scripts ...Script) (*NativeScript, error) {
 	nativeScripts := make([]NativeScript, len(scripts))
 	for i, script := range scripts {
 		nativeScript, ok := script.(*NativeScript)
 		if !ok {
-			return nil
+			return nil, errors.New("script must be a *NativeScript")
 		}
 		nativeScripts[i] = *nativeScript
 	}
 	concrete := &NativeScriptAll{
 		Type:    1,
 		Scripts: nativeScripts,
 	}
 	cborData, err := cbor.Encode(concrete)
 	if err != nil {
-		return nil
+		return nil, err
 	}
 	var script NativeScript
 	if _, err := cbor.Decode(cborData, &script); err != nil {
-		return nil
+		return nil, err
 	}
-	return &script
+	return &script, nil
 }

Then update all callers to handle the returned error.

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +1004 to +1013
scripts := make([]Script, len(keyHashes))
for i, keyHash := range keyHashes {
script, err := NewScriptSig(keyHash)
if err != nil {
return nil, err
}
scripts[i] = script
}

return NewScriptNOf(required, scripts...), nil
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential (nil, nil) return on CBOR encoding failure.

When NewScriptSig succeeds but NewScriptNOf returns nil due to a CBOR encoding error, this function returns (nil, nil), which is ambiguous for callers.

This is a consequence of the inconsistent error handling in the script constructor functions. Once those functions are updated to return errors (per the previous comment), update this code to propagate the error:

 	scripts := make([]Script, len(keyHashes))
 	for i, keyHash := range keyHashes {
 		script, err := NewScriptSig(keyHash)
 		if err != nil {
 			return nil, err
 		}
 		scripts[i] = script
 	}

-	return NewScriptNOf(required, scripts...), nil
+	result, err := NewScriptNOf(required, scripts...)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create N-of script: %w", err)
+	}
+	return result, nil

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In bursa.go around lines 1004 to 1013, the call to NewScriptNOf assumes it
always returns a valid Script and can lead to an ambiguous (nil, nil) when CBOR
encoding fails; change the code to capture both return values (script, err :=
NewScriptNOf(required, scripts...)), check err and return nil, err on error, and
also guard against a nil script by returning a descriptive error if script ==
nil but err == nil so callers never receive (nil, nil).

Signed-off-by: Chris Gianelloni <[email protected]>
@wolf31o2 wolf31o2 force-pushed the feat/cip-1854-multisig-support branch from 846aa04 to 37830d9 Compare December 11, 2025 00:20
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
internal/api/api.go (1)

133-141: Fix Required field validation: current tag still rejects valid all/any scripts.

The validation tag on Required is effectively still:

  • required_if=Type nOf (correct), and
  • min=1 unconditionally (because omitempty is glued into the param string, not a separate rule).

For Type="all" or "any", clients typically omit required; it deserializes as 0 and min=1 will fail, so valid requests are rejected unless they set a meaningless required value.

You want omitempty as its own rule so that min=1 is skipped when the field is zero for non‑nOf types. For example:

 type ScriptCreateRequest struct {
-	Type           string   `json:"type"                      validate:"required,oneof=nOf all any"`
-	Required       int      `json:"required"                  validate:"required_if=Type nOf,omitempty,min=1"`
+	Type           string   `json:"type"                      validate:"required,oneof=nOf all any"`
+	Required       int      `json:"required"                  validate:"omitempty,min=1,required_if=Type nOf"`
 	KeyHashes      []string `json:"key_hashes"                validate:"required,min=1,dive,hexadecimal,len=56"`
 	TimelockBefore uint64   `json:"timelock_before,omitempty"                                                   swaggertype:"integer" format:"int64"`
 	TimelockAfter  uint64   `json:"timelock_after,omitempty"                                                    swaggertype:"integer" format:"int64"`
 	Network        string   `json:"network"                   validate:"required,oneof=mainnet testnet"`
 }

This ensures:

  • For nOf, Required must be present and ≥1.
  • For all/any, Required can be omitted and won’t trigger min=1.
🧹 Nitpick comments (5)
docs/swagger.yaml (1)

28-32: Consider adding maxItems constraint to key_hashes array.

The key_hashes array has minItems: 1 but no upper bound. While the static analysis hint (CKV_OPENAPI_21) flags this, it's worth considering whether an unbounded array could pose a resource concern. A practical upper limit (e.g., 100 or 1000) would prevent potential abuse while still supporting legitimate use cases.

       key_hashes:
         items:
           type: string
         minItems: 1
+        maxItems: 100
         type: array
bursa_test.go (1)

957-979: Use realistic 28-byte key hashes in these tests.

Both TestGetScriptHash and TestGetScriptAddress create scripts with 3-byte key hashes ([]byte{0x01, 0x02, 0x03}), but Cardano uses Blake2b-224 hashes which are 28 bytes. While NewScriptSig doesn't currently validate hash length, using realistic 28-byte key hashes would make tests more representative of actual usage and better guard against future validation additions. See NewMultiSigScriptFromKeys (line 1066+) for how real key hashes are created.

openapi/docs/ApiScriptAddressRequest.md (1)

5-8: Fix markdown table to satisfy markdownlint (pipe style / column count).

markdownlint is flagging the properties table for ApiScriptAddressRequest (unexpected leading/trailing pipes and column count mismatch). You can normalize the table to 4 columns without leading/trailing pipes to clear those warnings. For example:

-Name | Type | Description | Notes |
------------- | ------------- | ------------- | ------------- |
-**Network** | **string** |  |  |
-**Script** | **map[string]map[string]interface{}** |  |  |
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**Network** | **string** |  |
+**Script** | **map[string]map[string]interface{}** |  |
internal/api/api.go (1)

1056-1169: handleScriptValidate implements the expected validation semantics; Network is currently informational.

  • The handler validates the structure of script, optional network (mainnet|testnet), signatures as 128‑char hex, and optional slot.
  • Script parsing via bursa.UnmarshalScript and validation via bursa.ValidateScript plus GetScriptHash produce ScriptValidateResponse with scriptHash, signature count, slot, and validity, matching the public schema.
  • Network is accepted/validated but not used in the logic yet; that’s fine if it’s intentionally reserved for future network-specific checks, but if you don’t plan to use it, you might consider documenting it as informational or trimming it from the request to avoid confusion.

Overall the flow looks correct.

openapi/api/openapi.yaml (1)

290-321: Consider adding maximum constraint for required field.

The required field has minimum: 1 but no maximum constraint. For nOf scripts, required should not exceed the number of key_hashes. Consider adding a schema-level constraint or documenting that backend validation enforces required ≤ len(key_hashes).

         required:
           minimum: 1
+          # Note: Must not exceed the length of key_hashes array
           type: integer
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 846aa04 and 37830d9.

📒 Files selected for processing (30)
  • bursa.go (5 hunks)
  • bursa_test.go (2 hunks)
  • cmd/bursa/main.go (1 hunks)
  • cmd/bursa/script.go (1 hunks)
  • docs/docs.go (3 hunks)
  • docs/swagger.json (3 hunks)
  • docs/swagger.yaml (2 hunks)
  • internal/api/api.go (5 hunks)
  • internal/api/api_test.go (1 hunks)
  • internal/cli/cli.go (2 hunks)
  • openapi.sh (1 hunks)
  • openapi/.openapi-generator/FILES (2 hunks)
  • openapi/README.md (2 hunks)
  • openapi/api/openapi.yaml (6 hunks)
  • openapi/api_default.go (14 hunks)
  • openapi/docs/ApiScriptAddressRequest.md (1 hunks)
  • openapi/docs/ApiScriptAddressResponse.md (1 hunks)
  • openapi/docs/ApiScriptCreateRequest.md (1 hunks)
  • openapi/docs/ApiScriptResponse.md (1 hunks)
  • openapi/docs/ApiScriptValidateRequest.md (1 hunks)
  • openapi/docs/ApiScriptValidateResponse.md (1 hunks)
  • openapi/docs/BursaWallet.md (2 hunks)
  • openapi/docs/DefaultAPI.md (2 hunks)
  • openapi/model_api_script_address_request.go (1 hunks)
  • openapi/model_api_script_address_response.go (1 hunks)
  • openapi/model_api_script_create_request.go (1 hunks)
  • openapi/model_api_script_response.go (1 hunks)
  • openapi/model_api_script_validate_request.go (1 hunks)
  • openapi/model_api_script_validate_response.go (1 hunks)
  • openapi/model_bursa_wallet.go (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • openapi/docs/ApiScriptValidateResponse.md
🚧 Files skipped from review as they are similar to previous changes (5)
  • openapi.sh
  • openapi/model_api_script_address_response.go
  • cmd/bursa/main.go
  • openapi/docs/ApiScriptResponse.md
  • openapi/api_default.go
🧰 Additional context used
🧬 Code graph analysis (7)
bursa_test.go (1)
bursa.go (31)
  • NewScriptSig (858-872)
  • NewScriptAll (875-897)
  • NewScriptAny (900-922)
  • NewScriptNOf (925-951)
  • NewScriptBefore (954-968)
  • NewScriptAfter (971-985)
  • GetScriptHash (1087-1099)
  • GetScriptAddress (1102-1124)
  • ErrInvalidNetwork (47-47)
  • GetRootKeyFromMnemonic (431-445)
  • GetMultiSigAccountKey (763-773)
  • GetMultiSigPaymentKey (776-784)
  • GetMultiSigPaymentVKey (811-817)
  • GetMultiSigPaymentSKey (820-826)
  • GetMultiSigStakeKey (829-837)
  • GetMultiSigStakeVKey (840-846)
  • GetMultiSigStakeSKey (849-855)
  • ValidateScript (1129-1156)
  • NewMultiSigScript (989-1014)
  • NativeScriptNofK (77-77)
  • NewAllMultiSigScript (1017-1032)
  • NativeScriptAll (75-75)
  • NewAnyMultiSigScript (1035-1050)
  • NativeScriptAny (76-76)
  • NewTimelockedScript (1053-1064)
  • NativeScript (73-73)
  • NewMultiSigScriptFromKeys (1068-1084)
  • MarshalScript (1531-1559)
  • UnmarshalScript (1562-1567)
  • ScriptData (1523-1528)
  • Script (69-69)
openapi/model_api_script_address_request.go (2)
openapi/utils.go (1)
  • MappedNullable (354-356)
bursa.go (1)
  • Script (69-69)
internal/api/api.go (2)
bursa.go (10)
  • Script (69-69)
  • NewMultiSigScript (989-1014)
  • NewAllMultiSigScript (1017-1032)
  • NewAnyMultiSigScript (1035-1050)
  • NewTimelockedScript (1053-1064)
  • MarshalScript (1531-1559)
  • UnmarshalScript (1562-1567)
  • ScriptData (1523-1528)
  • GetScriptHash (1087-1099)
  • GetScriptAddress (1102-1124)
internal/logging/logging.go (1)
  • GetLogger (78-83)
openapi/model_api_script_create_request.go (1)
openapi/utils.go (2)
  • MappedNullable (354-356)
  • IsNil (335-352)
openapi/model_api_script_validate_request.go (2)
openapi/utils.go (2)
  • MappedNullable (354-356)
  • IsNil (335-352)
bursa.go (1)
  • Script (69-69)
openapi/model_bursa_wallet.go (2)
openapi/model_bursa_key_file.go (1)
  • BursaKeyFile (22-26)
openapi/utils.go (1)
  • IsNil (335-352)
bursa.go (1)
bip32/bip32.go (2)
  • XPrv (43-43)
  • PublicKey (49-49)
🪛 Checkov (3.2.334)
docs/swagger.yaml

[medium] 29-34: Ensure that arrays have a maximum number of items

(CKV_OPENAPI_21)

🪛 LanguageTool
openapi/docs/ApiScriptValidateRequest.md

[grammar] ~57-~57: Ensure spelling is correct
Context: ...a boolean if a field has been set. ### GetRequireSignatures func (o *ApiScriptValidateRequest) GetRequireSignatures() bool GetRequireSignatures returns the Require...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~63-~63: Ensure spelling is correct
Context: ... if non-nil, zero value otherwise. ### GetRequireSignaturesOk func (o *ApiScriptValidateRequest) GetRequireSignaturesOk() (*bool, bool) GetRequireSignaturesOk returns a tuple w...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🪛 markdownlint-cli2 (0.18.1)
openapi/docs/ApiScriptAddressRequest.md

7-7: Table pipe style
Expected: no_leading_or_trailing; Actual: trailing_only; Unexpected trailing pipe

(MD055, table-pipe-style)


7-7: Table column count
Expected: 4; Actual: 3; Too few cells, row will be missing data

(MD056, table-column-count)


8-8: Table pipe style
Expected: no_leading_or_trailing; Actual: trailing_only; Unexpected trailing pipe

(MD055, table-pipe-style)


8-8: Table column count
Expected: 4; Actual: 3; Too few cells, row will be missing data

(MD056, table-column-count)

openapi/README.md

95-95: Unordered list indentation
Expected: 0; Actual: 1

(MD007, ul-indent)


96-96: Unordered list indentation
Expected: 0; Actual: 1

(MD007, ul-indent)


97-97: Unordered list indentation
Expected: 0; Actual: 1

(MD007, ul-indent)


98-98: Unordered list indentation
Expected: 0; Actual: 1

(MD007, ul-indent)


99-99: Unordered list indentation
Expected: 0; Actual: 1

(MD007, ul-indent)


100-100: Unordered list indentation
Expected: 0; Actual: 1

(MD007, ul-indent)

openapi/docs/ApiScriptCreateRequest.md

7-7: Table pipe style
Expected: no_leading_or_trailing; Actual: trailing_only; Unexpected trailing pipe

(MD055, table-pipe-style)


7-7: Table column count
Expected: 4; Actual: 3; Too few cells, row will be missing data

(MD056, table-column-count)


8-8: Table pipe style
Expected: no_leading_or_trailing; Actual: trailing_only; Unexpected trailing pipe

(MD055, table-pipe-style)


8-8: Table column count
Expected: 4; Actual: 3; Too few cells, row will be missing data

(MD056, table-column-count)


12-12: Table pipe style
Expected: no_leading_or_trailing; Actual: trailing_only; Unexpected trailing pipe

(MD055, table-pipe-style)


12-12: Table column count
Expected: 4; Actual: 3; Too few cells, row will be missing data

(MD056, table-column-count)

openapi/docs/ApiScriptValidateRequest.md

9-9: Table pipe style
Expected: no_leading_or_trailing; Actual: trailing_only; Unexpected trailing pipe

(MD055, table-pipe-style)


9-9: Table column count
Expected: 4; Actual: 3; Too few cells, row will be missing data

(MD056, table-column-count)

openapi/docs/DefaultAPI.md

33-33: Hard tabs
Column: 1

(MD010, no-hard-tabs)


34-34: Hard tabs
Column: 1

(MD010, no-hard-tabs)


35-35: Hard tabs
Column: 1

(MD010, no-hard-tabs)


36-36: Hard tabs
Column: 1

(MD010, no-hard-tabs)


40-40: Hard tabs
Column: 1

(MD010, no-hard-tabs)


42-42: Hard tabs
Column: 1

(MD010, no-hard-tabs)


43-43: Hard tabs
Column: 1

(MD010, no-hard-tabs)


44-44: Hard tabs
Column: 1

(MD010, no-hard-tabs)


45-45: Hard tabs
Column: 1

(MD010, no-hard-tabs)


46-46: Hard tabs
Column: 1

(MD010, no-hard-tabs)


47-47: Hard tabs
Column: 1

(MD010, no-hard-tabs)


48-48: Hard tabs
Column: 1

(MD010, no-hard-tabs)


49-49: Hard tabs
Column: 1

(MD010, no-hard-tabs)


50-50: Hard tabs
Column: 1

(MD010, no-hard-tabs)


65-65: Table pipe style
Expected: no_leading_or_trailing; Actual: trailing_only; Unexpected trailing pipe

(MD055, table-pipe-style)


65-65: Table column count
Expected: 4; Actual: 3; Too few cells, row will be missing data

(MD056, table-column-count)


80-80: No empty links

(MD042, no-empty-links)


99-99: Hard tabs
Column: 1

(MD010, no-hard-tabs)


100-100: Hard tabs
Column: 1

(MD010, no-hard-tabs)


101-101: Hard tabs
Column: 1

(MD010, no-hard-tabs)


102-102: Hard tabs
Column: 1

(MD010, no-hard-tabs)


106-106: Hard tabs
Column: 1

(MD010, no-hard-tabs)


108-108: Hard tabs
Column: 1

(MD010, no-hard-tabs)


109-109: Hard tabs
Column: 1

(MD010, no-hard-tabs)


110-110: Hard tabs
Column: 1

(MD010, no-hard-tabs)


111-111: Hard tabs
Column: 1

(MD010, no-hard-tabs)


112-112: Hard tabs
Column: 1

(MD010, no-hard-tabs)


113-113: Hard tabs
Column: 1

(MD010, no-hard-tabs)


114-114: Hard tabs
Column: 1

(MD010, no-hard-tabs)


115-115: Hard tabs
Column: 1

(MD010, no-hard-tabs)


116-116: Hard tabs
Column: 1

(MD010, no-hard-tabs)


131-131: Table pipe style
Expected: no_leading_or_trailing; Actual: trailing_only; Unexpected trailing pipe

(MD055, table-pipe-style)


131-131: Table column count
Expected: 4; Actual: 3; Too few cells, row will be missing data

(MD056, table-column-count)


146-146: No empty links

(MD042, no-empty-links)


165-165: Hard tabs
Column: 1

(MD010, no-hard-tabs)


166-166: Hard tabs
Column: 1

(MD010, no-hard-tabs)


167-167: Hard tabs
Column: 1

(MD010, no-hard-tabs)


168-168: Hard tabs
Column: 1

(MD010, no-hard-tabs)


172-172: Hard tabs
Column: 1

(MD010, no-hard-tabs)


174-174: Hard tabs
Column: 1

(MD010, no-hard-tabs)


175-175: Hard tabs
Column: 1

(MD010, no-hard-tabs)


176-176: Hard tabs
Column: 1

(MD010, no-hard-tabs)


177-177: Hard tabs
Column: 1

(MD010, no-hard-tabs)


178-178: Hard tabs
Column: 1

(MD010, no-hard-tabs)


179-179: Hard tabs
Column: 1

(MD010, no-hard-tabs)


180-180: Hard tabs
Column: 1

(MD010, no-hard-tabs)


181-181: Hard tabs
Column: 1

(MD010, no-hard-tabs)


182-182: Hard tabs
Column: 1

(MD010, no-hard-tabs)


197-197: Table pipe style
Expected: no_leading_or_trailing; Actual: trailing_only; Unexpected trailing pipe

(MD055, table-pipe-style)


197-197: Table column count
Expected: 4; Actual: 3; Too few cells, row will be missing data

(MD056, table-column-count)


212-212: No empty links

(MD042, no-empty-links)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: docker
  • GitHub Check: go-test (1.24.x, ubuntu-latest)
  • GitHub Check: Analyze (go)
🔇 Additional comments (48)
openapi/README.md (1)

82-100: Documentation correctly reflects the new Script API surface.

The new API endpoints and model references are properly documented and align with the OpenAPI specification updates for the CIP-1854 multi-signature feature.

openapi/docs/ApiScriptAddressResponse.md (1)

1-107: Auto-generated model documentation is complete.

The ApiScriptAddressResponse model documentation correctly describes the response structure for the script address endpoint with all optional fields properly documented.

openapi/.openapi-generator/FILES (1)

8-29: Generator manifest correctly updated with new script model files.

The FILES manifest properly tracks all new script-related model and documentation files generated for the CIP-1854 feature.

bursa.go (7)

1052-1064: NewTimelockedScript inherits the timelock naming confusion.

The before parameter's meaning is unclear due to the underlying timelock function naming issue. Once the timelock functions are corrected, this wrapper should be reviewed for clarity.


67-99: Script type aliases and GetScriptType helper are well-implemented.

The type aliases provide clean integration with gouroboros types, and GetScriptType safely handles edge cases (nil, non-NativeScript, empty CBOR).


762-855: CIP-1854 multi-sig key derivation helpers follow established patterns.

The derivation path 1854'/1815'/account' correctly implements CIP-1854, and the helper functions (GetMultiSigPaymentVKey, GetMultiSigPaymentSKey, etc.) reuse existing key file creation patterns for consistency.


857-872: NewScriptSig properly returns errors unlike other script constructors.

This function correctly returns (*NativeScript, error), providing clear error handling. The other script constructors (NewScriptAll, NewScriptAny, NewScriptNOf) return nil on error without error information—a pattern noted in past reviews.


1086-1124: Script hashing and address generation are correctly implemented.

GetScriptHash uses Blake2b-224 as specified for Cardano script hashes, and GetScriptAddress properly creates AddressTypeScriptNone addresses. Good null checks and error propagation.


1522-1638: ScriptData serialization is well-structured.

MarshalScript and scriptToMap handle all native script types with proper recursive serialization. The optional network parameter for address generation is a good API design choice.


1640-1770: mapToScript deserialization properly validates inputs.

Good input validation including:

  • Integer check for n (line 1711-1713)
  • Range check for n (line 1719)
  • Non-negative integer check for slots (lines 1748-1752, 1760-1763)

Previous review concerns about non-integral values have been addressed.

openapi/docs/ApiScriptCreateRequest.md (1)

1-171: Auto-generated model documentation is complete and accurate.

The ApiScriptCreateRequest documentation correctly describes the request structure for script creation with required fields (KeyHashes, Network, Type) and optional fields (Required, TimelockAfter, TimelockBefore). The static analysis warnings about table formatting are false positives—the format is consistent with OpenAPI generator output.

openapi/docs/ApiScriptValidateRequest.md (1)

1-155: Auto-generated OpenAPI documentation looks correct.

This file accurately documents the ApiScriptValidateRequest model with its properties and accessor methods. The static analysis hints about table formatting and grammar are false positives typical of auto-generated OpenAPI docs.

internal/api/api_test.go (4)

658-691: Good test coverage for script creation endpoint.

The test validates the happy path for creating a 2-of-3 multi-sig script with proper assertions on response status and JSON field presence.


693-732: Script validation test properly verifies the response structure.

Good practice to decode the JSON response and explicitly assert valid == true rather than just checking for string presence.


734-818: Comprehensive error case coverage for script validation.

The table-driven tests cover important error scenarios including invalid hex in keyHash, missing required fields, invalid network, and unsupported script types. The test case name "invalid keyHash format" on line 742 correctly describes the test intent.


820-855: Script address generation test validates mainnet address prefix.

Good test that verifies the response contains expected fields and that the generated address starts with addr1 for mainnet.

docs/swagger.yaml (1)

41-46: Timelock fields now correctly use int64 format.

The timelock_after and timelock_before fields now include format: int64, matching the slot field in ScriptValidateRequest. This ensures consistent handling of Cardano slot numbers that may exceed 32-bit limits.

cmd/bursa/script.go (4)

22-34: Well-structured root script command with subcommands.

The command hierarchy is clean with appropriate short descriptions.


36-97: Script create command implementation looks good.

The flag definitions align with the CLI handler, and the help text provides clear usage examples. The variable any is renamed to useAny to avoid shadowing Go's built-in any type alias—good practice.


99-143: Script validate command correctly implements structural-only mode.

The help text examples now accurately describe the --structural-only flag for timelock-only validation, addressing the previous review feedback.


145-177: Script address command is straightforward.

Clean implementation with required --script flag and sensible mainnet default for network.

bursa_test.go (6)

18-18: Import added for Ed25519 key generation in tests.

Needed for TestMultiSigScriptFromKeys to generate test public keys.


882-955: Comprehensive script type tests with proper CBOR verification.

Tests cover all script types (sig, all, any, nOf, before, after) and verify both constructor behavior and CBOR encoding output.


1080-1118: Good coverage of multi-sig script construction and timelocking.

Tests verify correct script type identification and internal structure for 2-of-3, All, Any, and timelocked scripts.


1120-1136: Ed25519 key-based script creation test is well-designed.

Uses ed25519.GenerateKey(nil) with nil reader for deterministic test keys, then verifies the resulting script structure.


1292-1322: Timelock boundary tests correctly verify Cardano semantics.

  • ScriptBefore(1000) is correctly validated as false at slot 1000 (must be < threshold)
  • ScriptAfter(1000) is correctly validated as true at slot 1000 (must be >= threshold)

This aligns with Cardano's RequireTimeAfter semantics.


1324-1406: Round-trip marshaling tests provide good coverage.

Tests verify that scripts can be serialized and deserialized without data loss, and properly handle invalid input types.

openapi/docs/DefaultAPI.md (1)

1-215: New script API endpoints are properly documented.

The auto-generated documentation correctly describes the three new endpoints (/api/script/address, /api/script/create, /api/script/validate) with accurate request/response types and usage examples. The static analysis hints about tabs and table formatting are expected artifacts of the OpenAPI generator.

internal/cli/cli.go (6)

126-161: Comprehensive parameter validation addresses previous review feedback.

The validation logic correctly:

  • Prevents mutually exclusive flags (--all and --any, lines 136-139)
  • Prevents --required with --all or --any (lines 140-147)
  • Requires at least one flag type (lines 148-151)
  • Validates at least one key hash (lines 152-155)
  • Prevents both timelocks from being specified (lines 156-161)

180-190: Validation ensures required doesn't exceed key hash count.

This addresses the previous review feedback about preventing invalid script construction.


221-258: File handling now properly checks close errors.

The explicit file.Close() with error check (lines 243-247) before logging success (line 248) addresses the previous review feedback about potentially misleading success messages and unchecked close errors.


260-295: Script signature requirement detection is well-implemented.

The scriptRequiresSignatures function correctly traverses nested scripts to determine if any component requires signatures, with anyScriptRequiresSignatures using the idiomatic slices.ContainsFunc helper.


332-338: Script hash is now recomputed from the parsed script.

This addresses the previous review feedback about trusting the hash from the input file. Computing a fresh hash ensures integrity even if the file was modified.


386-450: Script address generation follows the same robust pattern.

Like RunScriptValidate, this function recomputes the script hash from the unmarshaled script rather than trusting the input file's hash field.

docs/swagger.json (2)

22-150: Script endpoint path specs look consistent with handlers.

The new /api/script/address, /api/script/create, and /api/script/validate paths correctly reference the corresponding api.Script*Request/Response models and use POST with the same summaries/descriptions as in internal/api/api.go. No issues from a contract/shape perspective.


362-507: Script-related definitions and bursa.KeyFile/bursa.Wallet updates are coherent.

  • api.Script* definitions (request/response) match the JSON tags and types used in the Go models and handlers, including scriptHash naming and timelock_*/slot as integer with format: int64.
  • api.ScriptValidateRequest only requires script, mirroring the now-optional Network field in the handler.
  • Updated bursa.KeyFile/bursa.Wallet schemas (examples removed, types kept) remain compatible with existing handlers.

Looks good as the canonical Swagger source for the new script functionality.

Also applies to: 603-678

openapi/model_api_script_create_request.go (1)

20-292: ApiScriptCreateRequest model and JSON validation look correct.

The generated model matches the Swagger schema: key_hashes, network, and type are enforced as required in UnmarshalJSON, while required and timelock fields stay optional with pointer types. DisallowUnknownFields is a nice touch to keep payloads strict.

openapi/model_api_script_validate_response.go (1)

18-237: ApiScriptValidateResponse matches the documented response shape.

Pointer fields with omitempty and ToMap()-based marshalling align with the Swagger definition (scriptHash, signatures, slot, valid), and will happily consume the server’s always-present values as non-nil pointers. Looks fine.

openapi/model_api_script_response.go (1)

18-235: ApiScriptResponse struct aligns with api.ScriptResponse schema.

Address/script/scriptHash/type fields and JSON tags match the Swagger definition. Nullable wrappers and ToMap() follow the usual openapi-generator pattern; nothing problematic here.

openapi/model_api_script_address_request.go (1)

20-195: ApiScriptAddressRequest JSON handling is strict and consistent.

Required-property checks for network and script plus DisallowUnknownFields give you a tight client-side model that matches the Swagger definition. Server-side network validation via enums/validators can stay as-is.

docs/docs.go (1)

26-154: Swag template correctly mirrors new script API surface.

The /api/script/address|create|validate paths and the api.Script* definitions in docTemplate match the JSON Swagger spec and the handler structs in internal/api/api.go (network enums, timelock formats, scriptHash naming, etc.). bursa.KeyFile/bursa.Wallet are also aligned. Regeneration pipeline looks coherent.

Also applies to: 366-511, 607-682

internal/api/api.go (2)

535-540: WalletRestore error response now safely JSON-encodes err.Error().

Wrapping the error string in a map[string]string and marshaling with json.Marshal avoids JSON injection/malformed responses while still surfacing a detailed client error, with a reasonable fallback message if marshaling fails. This is a solid hardening over direct string interpolation.


1171-1267: handleScriptAddress correctly validates input and returns a typed response.

  • Validates script + network (mainnet|testnet) before processing.
  • Reuses bursa.UnmarshalScript then bursa.GetScriptAddress and bursa.GetScriptHash, returning ScriptAddressResponse with address, network, and hex-encoded scriptHash, in line with the Swagger and OpenAPI models.
  • Error responses for invalid JSON, validation failures, bad script format, address-generation errors, and hash errors are consistently JSON-encoded with safe marshaling.

No functional issues spotted here.

openapi/model_api_script_validate_request.go (2)

20-30: LGTM! Slot field correctly uses int64.

The Slot field now uses *int64, which correctly addresses the previous comment about Cardano slot numbers potentially overflowing int32 (max 2,147,483,647). This aligns with the OpenAPI spec's format: int64 specification.


234-272: LGTM! Proper required field and unknown field validation.

The UnmarshalJSON implementation correctly validates that required fields are present and rejects unknown fields, following OpenAPI best practices for strict schema enforcement.

openapi/model_bursa_wallet.go (1)

23-41: LGTM! Consistent implementation of new wallet key fields.

The nine new committee and drep key fields are implemented consistently with existing Payment/Stake field patterns. All accessor methods (Get/GetOk/Has/Set) are complete, and ToMap correctly handles optional field serialization.

Also applies to: 60-346, 646-672

openapi/api/openapi.yaml (1)

16-108: LGTM! Script endpoints follow consistent API patterns.

The three new script endpoints (/address, /create, /validate) follow the established pattern for the API with consistent request/response structures and appropriate HTTP status codes.

openapi/docs/BursaWallet.md (1)

7-269: LGTM! Documentation accurately reflects expanded BursaWallet model.

The documentation correctly describes the nine new committee and drep key fields along with their accessor methods, maintaining consistency with the existing field documentation patterns.

Comment on lines +953 to +985
// NewScriptBefore creates a "before" script valid before the given slot
func NewScriptBefore(slot uint64) *NativeScript {
concrete := &NativeScriptInvalidBefore{
Type: 4,
Slot: slot,
}
cborData, err := cbor.Encode(concrete)
if err != nil {
return nil
}
var script NativeScript
if _, err := cbor.Decode(cborData, &script); err != nil {
return nil
}
return &script
}

// NewScriptAfter creates an "after" script valid after the given slot
func NewScriptAfter(slot uint64) *NativeScript {
concrete := &NativeScriptInvalidHereafter{
Type: 5,
Slot: slot,
}
cborData, err := cbor.Encode(concrete)
if err != nil {
return nil
}
var script NativeScript
if _, err := cbor.Decode(cborData, &script); err != nil {
return nil
}
return &script
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Cardano CIP NativeScriptInvalidBefore InvalidHereafter type 4 type 5 semantics

💡 Result:

Summary — native-script constructor tags and semantics

  • Constructor tags (CBOR/Phase‑1 native scripts): 0 = script_pubkey (sig), 1 = script_all (all), 2 = script_any (any), 3 = script_n_of_k (n-of-k), 4 = invalid_before, 5 = invalid_hereafter. [1][2]

  • Semantics of types 4 and 5:

    • invalid_before (tag 4): specifies the left (included) endpoint a of the validity interval — the transaction slot must be >= a.
    • invalid_hereafter (tag 5): specifies the right (excluded) endpoint b of the validity interval — the transaction slot must be < b.
    • Together they define a half‑open interval [a, b) for when the script can succeed. Values are expressed as Cardano slot numbers. [2][3]

Sources

  1. CIP‑0029 (Phase‑1 native scripts specification / CBOR/CDDL). [cited for overall constructor tags]. [1]
  2. Cardano StackExchange explanation quoting the CDDL showing script_pubkey..invalid_before..invalid_hereafter and the half‑open interval comment. [2]
  3. Native‑script docs / libraries (pycardano / cardano‑c) showing InvalidBefore / InvalidHereAfter constructors and slot semantics. [3]

Timelock function implementations are semantically inverted from their names.

According to Cardano CIP-0029, the native script types have these meanings:

  • NativeScriptInvalidBefore (type 4): Script succeeds when slot >= specified slot (valid at or after)
  • NativeScriptInvalidHereafter (type 5): Script succeeds when slot < specified slot (valid before)

The current implementation swaps these:

  • NewScriptBefore uses NativeScriptInvalidBefore (type 4) → creates a script valid after the slot
  • NewScriptAfter uses NativeScriptInvalidHereafter (type 5) → creates a script valid before the slot

This contradicts the documented function behavior. Either swap the type assignments to match the function names, or rename the functions to NewScriptValidAfter/NewScriptValidBefore to match their actual semantics.

🤖 Prompt for AI Agents
In bursa.go around lines 953 to 985, the timelock creators are semantically
inverted: NewScriptBefore currently constructs a NativeScriptInvalidBefore (type
4) which per CIP-0029 is valid at/after the slot, and NewScriptAfter constructs
NativeScriptInvalidHereafter (type 5) which is valid before the slot; fix by
swapping the types used (or swap the concrete struct types used in each
function) so NewScriptBefore produces a script that is valid before the given
slot (use NativeScriptInvalidHereafter/type 5) and NewScriptAfter produces a
script that is valid after the given slot (use NativeScriptInvalidBefore/type
4), keeping CBOR encode/decode flow and error handling unchanged.

Comment on lines +1248 to +1259
// validateScriptBefore checks if current slot is before the specified slot
func validateScriptBefore(script *NativeScriptInvalidBefore, slot uint64) bool {
return slot < script.Slot
}

// validateScriptAfter checks if current slot is after the specified slot
func validateScriptAfter(
script *NativeScriptInvalidHereafter,
slot uint64,
) bool {
return slot >= script.Slot
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validator logic matches inverted naming convention.

These validators are consistent with the current naming but will need updates if the timelock function naming is corrected per the previous comment.

Note: validateScriptBefore checking slot < script.Slot with NativeScriptInvalidBefore is semantically checking "is current slot before the 'valid after' threshold" which returns true when the script is not yet valid. This appears inverted from expected behavior.

🤖 Prompt for AI Agents
bursa.go lines 1248-1259: The validator logic is inverted for the timelock
checks — NativeScriptInvalidBefore should be considered valid when current slot
is at or after the script.Slot, and NativeScriptInvalidHereafter should be valid
when current slot is before the script.Slot; change validateScriptBefore to
return slot >= script.Slot and change validateScriptAfter to return slot <
script.Slot (and adjust any related tests or callers accordingly).

Comment on lines +898 to +1054
// handleScriptCreate godoc
//
// @Summary Create a multi-signature script
// @Description Create a new multi-signature script with the specified parameters
// @Accept json
// @Produce json
// @Param request body ScriptCreateRequest true "Script Create Request"
// @Success 200 {object} ScriptResponse "Script successfully created"
// @Failure 400 {string} string "Invalid request"
// @Failure 500 {string} string "Internal server error"
// @Router /api/script/create [post]
func handleScriptCreate(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}

var req ScriptCreateRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte(`{"error":"Invalid JSON request"}`))
return
}

// Validate the request
if err := validate.Struct(req); err != nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
var validationErrors validator.ValidationErrors
if errors.As(err, &validationErrors) {
_, _ = fmt.Fprintf(
w,
`{"error":"Validation failed","fields":%s}`,
formatValidationErrors(validationErrors),
)
} else {
_, _ = w.Write([]byte(`{"error":"Invalid request format"}`))
}
return
}

logger := logging.GetLogger()

// Parse key hashes
hashes := make([][]byte, len(req.KeyHashes))
for i, hashStr := range req.KeyHashes {
hash, err := hex.DecodeString(hashStr)
if err != nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
errorResp := map[string]string{
"error": "Invalid key hash format: " + hashStr,
}
if jsonBytes, jsonErr := json.Marshal(errorResp); jsonErr == nil {
_, _ = w.Write(jsonBytes)
} else {
_, _ = w.Write([]byte(`{"error":"Invalid key hash format"}`))
}
return
}
hashes[i] = hash
}

// Create the script
var script bursa.Script
var err error
switch req.Type {
case "nOf":
if req.Required <= 0 || req.Required > len(hashes) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write(
[]byte(`{"error":"Invalid required signatures count"}`),
)
return
}
script, err = bursa.NewMultiSigScript(req.Required, hashes...)
case "all":
script, err = bursa.NewAllMultiSigScript(hashes...)
case "any":
script, err = bursa.NewAnyMultiSigScript(hashes...)
default:
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
errorResp := map[string]string{
"error": "Unsupported script type: " + req.Type,
}
if jsonBytes, jsonErr := json.Marshal(errorResp); jsonErr == nil {
_, _ = w.Write(jsonBytes)
} else {
_, _ = w.Write([]byte(`{"error":"Invalid script type"}`))
}
return
}
if err != nil {
logger.Error("failed to create script", "error", err)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
errorResp := map[string]string{"error": err.Error()}
if jsonBytes, jsonErr := json.Marshal(errorResp); jsonErr == nil {
_, _ = w.Write(jsonBytes)
} else {
_, _ = w.Write([]byte(`{"error":"Failed to create script"}`))
}
return
}

// Apply timelock if specified
if req.TimelockBefore > 0 && req.TimelockAfter > 0 {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
errorResp := map[string]string{
"error": "cannot specify both timelock_before and timelock_after",
}
if jsonBytes, jsonErr := json.Marshal(errorResp); jsonErr == nil {
_, _ = w.Write(jsonBytes)
} else {
_, _ = w.Write([]byte(`{"error":"Invalid timelock parameters"}`))
}
return
}
if req.TimelockBefore > 0 {
script = bursa.NewTimelockedScript(req.TimelockBefore, true, script)
} else if req.TimelockAfter > 0 {
script = bursa.NewTimelockedScript(req.TimelockAfter, false, script)
}

// Marshal script data
scriptData, err := bursa.MarshalScript(script, req.Network)
if err != nil {
logger.Error("failed to marshal script", "error", err)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(`{"error":"Internal server error"}`))
return
}

// Return ScriptResponse with consistent field naming
response := ScriptResponse{
Type: scriptData.Type,
Script: scriptData.Script,
Address: scriptData.Address,
ScriptHash: scriptData.Hash,
}

w.Header().Set("Content-Type", "application/json")
resp, err := json.Marshal(response)
if err != nil {
logger.Error("failed to serialize script response", "error", err)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(`{"error":"Internal server error"}`))
return
}
_, _ = w.Write(resp)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify ScriptCreateRequest validation usage
rg -n "ScriptCreateRequest" internal/api/api.go
rg -n "validate.Struct\(req\)" internal/api/api.go -C3

Repository: blinklabs-io/bursa

Length of output: 2029


🏁 Script executed:

#!/bin/bash
# Check ScriptCreateRequest struct definition and validation tags
sed -n '133,200p' internal/api/api.go

Repository: blinklabs-io/bursa

Length of output: 3211


🏁 Script executed:

#!/bin/bash
# Check validator library import and version
rg -n "import" internal/api/api.go | head -20
# Check for validator library
rg -n "validator" internal/api/api.go | head -10
# Check formatValidationErrors function
rg -n "formatValidationErrors" internal/api/api.go -B2 -A5

Repository: blinklabs-io/bursa

Length of output: 2719


🏁 Script executed:

#!/bin/bash
# Check if bursa module functions exist
rg -n "func.*NewMultiSigScript|func.*NewAllMultiSigScript|func.*NewAnyMultiSigScript|func.*NewTimelockedScript|func.*MarshalScript" 
# Also check import of bursa module
rg -n '".*bursa' internal/api/api.go

Repository: blinklabs-io/bursa

Length of output: 961


🏁 Script executed:

#!/bin/bash
# Check exact line numbers of handleScriptCreate function
rg -n "^func handleScriptCreate" internal/api/api.go
# Get the function boundaries and verify timelock logic
sed -n '898,1054p' internal/api/api.go | grep -n "timelock\|func" | head -20

Repository: blinklabs-io/bursa

Length of output: 383


🏁 Script executed:

#!/bin/bash
# Get full handleScriptCreate function (from line 909 to next function or end)
sed -n '909,1060p' internal/api/api.go | tail -100
# Check what comes after line 1054 to see if function ends there
sed -n '1050,1065p' internal/api/api.go

Repository: blinklabs-io/bursa

Length of output: 3689


🏁 Script executed:

#!/bin/bash
# Check the exact Required validation tag syntax
sed -n '134,142p' internal/api/api.go
# Also check if there are any tests or examples of how required_if is used
rg -n "required_if" internal/api/api.go

Repository: blinklabs-io/bursa

Length of output: 922


🏁 Script executed:

#!/bin/bash
# Check go-playground/validator v10 documentation or check if there are any go.mod references
cat go.mod | grep validator
# Also check if there are tests that validate the Required field
rg -n "Required.*nOf\|required.*nOf" --type go

Repository: blinklabs-io/bursa

Length of output: 110


🏁 Script executed:

#!/bin/bash
# Check for test files related to handleScriptCreate
find . -name "*test*.go" -type f | xargs grep -l "handleScriptCreate\|ScriptCreateRequest" 2>/dev/null
# Also check for any comments or issues related to the validation
rg -n "required_if.*Type\|TODO.*required\|FIXME.*required" --type go

Repository: blinklabs-io/bursa

Length of output: 88


🏁 Script executed:

#!/bin/bash
# Check api_test.go for ScriptCreateRequest tests
rg -n "ScriptCreateRequest\|handleScriptCreate\|Test.*Script.*Create" internal/api/api_test.go -A5 | head -50

Repository: blinklabs-io/bursa

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Let's verify the key code sections match the review claims
# Check the Required validation in context of the nOf case
sed -n '940,960p' internal/api/api.go
# Check the timelock validation and application
sed -n '1000,1030p' internal/api/api.go

Repository: blinklabs-io/bursa

Length of output: 1660


🏁 Script executed:

#!/bin/bash
# Check the actual Required field validation in the nOf case
# The handler validates Required at runtime with: if req.Required <= 0 || req.Required > len(hashes)
sed -n '955,970p' internal/api/api.go
# Also check if there's any documentation or reference to the required_if issue
rg -n "required_if\|Required.*nOf\|nOf.*Required" . --type go | grep -v node_modules

Repository: blinklabs-io/bursa

Length of output: 431


🏁 Script executed:

#!/bin/bash
# Let's verify the complete validation flow one more time
# Check if there are any related issues or TODOs
rg -n "TODO\|FIXME\|XXX\|HACK" internal/api/api.go | grep -i "required\|valid\|tag"
# Check if the validator is actually called with the struct
sed -n '920,940p' internal/api/api.go

Repository: blinklabs-io/bursa

Length of output: 581


handleScriptCreate implementation is solid, but the Required field validation tag needs correction.

  • Request validation mirrors the Swagger/OpenAPI contract and reuses the shared formatValidationErrors helper.
  • Key hashes are decoded via hex.DecodeString with per-hash 400s and JSON-encoded error messages, avoiding interpolation issues.
  • Script construction correctly branches on Type (nOf/all/any) and validates Required vs number of hashes in the nOf case via runtime check.
  • The timelock logic correctly rejects requests that specify both timelock_before and timelock_after, then wraps the script with a single before/after timelock as expected.
  • Response mapping via bursa.MarshalScript into ScriptResponse keeps JSON field names (scriptHash vs hash) consistent with the documented API.

However, the Required field validation tag validate:"required_if=Type nOf,omitempty,min=1" contains omitempty, which contradicts the required_if condition—the omitempty tag allows zero values even when Type is nOf. Remove omitempty and verify the required_if syntax is correct for go-playground/validator v10 (likely required_if=Type=nOf).

🤖 Prompt for AI Agents
internal/api/api.go lines 898-1054: the struct tag for Required mistakenly
includes `omitempty` which defeats the `required_if` rule and the `required_if`
syntax may be wrong; remove `omitempty` and update the tag to a valid
go-playground/validator v10 form (e.g. `validate:"required_if=Type nOf,min=1"`
or `validate:"required_if=Type=nOf,min=1"` depending on your validator version),
then run tests or a quick validation check to ensure the rule enforces presence
when Type is "nOf".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet