mirror of
https://github.com/luxfi/mpc.git
synced 2026-09-24 00:26:24 +00:00
fix: gofmt -s across repo (CI format check)
This commit is contained in:
@@ -11,8 +11,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-sev-guest/verify/trust"
|
||||
test "github.com/google/go-sev-guest/testing"
|
||||
"github.com/google/go-sev-guest/verify/trust"
|
||||
)
|
||||
|
||||
// sevSnpAttestationMilan is a real AMD Milan SEV-SNP attestation report
|
||||
|
||||
+2
-2
@@ -139,8 +139,8 @@ func (r *ComplianceRejection) Error() string { return "mpc: " + r.Reason }
|
||||
|
||||
// SignResult is the immediate response.
|
||||
type SignResult struct {
|
||||
SessionID string
|
||||
Signature []byte
|
||||
SessionID string
|
||||
Signature []byte
|
||||
// Status: pending | signed.
|
||||
Status string
|
||||
ApprovalID string
|
||||
|
||||
@@ -98,12 +98,12 @@ func airgapFIPSCheckCommand() *cli.Command {
|
||||
}
|
||||
provider := strings.ToLower(strings.TrimSpace(c.Args().Get(0)))
|
||||
validated := map[string]string{
|
||||
"aws": "AWS KMS / CloudHSM, CMVP cert #4523 / #3380",
|
||||
"gcp": "Google Cloud HSM (Marvell LiquidSecurity), CMVP cert #4399",
|
||||
"azure": "Azure Key Vault Premium / Managed HSM, CMVP cert #4399 / #4153",
|
||||
"yubihsm": "YubiHSM 2 (FIPS firmware), CMVP cert #4148",
|
||||
"pkcs11": "validation depends on configured vendor library — verify CMVP listing",
|
||||
"kmip": "validation depends on configured KMS server — verify CMVP listing",
|
||||
"aws": "AWS KMS / CloudHSM, CMVP cert #4523 / #3380",
|
||||
"gcp": "Google Cloud HSM (Marvell LiquidSecurity), CMVP cert #4399",
|
||||
"azure": "Azure Key Vault Premium / Managed HSM, CMVP cert #4399 / #4153",
|
||||
"yubihsm": "YubiHSM 2 (FIPS firmware), CMVP cert #4148",
|
||||
"pkcs11": "validation depends on configured vendor library — verify CMVP listing",
|
||||
"kmip": "validation depends on configured KMS server — verify CMVP listing",
|
||||
}
|
||||
if cert, ok := validated[provider]; ok {
|
||||
fmt.Printf("OK: %s — %s\n", provider, cert)
|
||||
|
||||
+2
-3
@@ -39,7 +39,6 @@ import (
|
||||
|
||||
mpcapi "github.com/luxfi/mpc/pkg/api"
|
||||
"github.com/luxfi/mpc/pkg/backup"
|
||||
"github.com/luxfi/mpc/pkg/zapauth"
|
||||
"github.com/luxfi/mpc/pkg/db"
|
||||
"github.com/luxfi/mpc/pkg/event"
|
||||
"github.com/luxfi/mpc/pkg/eventconsumer"
|
||||
@@ -50,6 +49,7 @@ import (
|
||||
"github.com/luxfi/mpc/pkg/mpc"
|
||||
"github.com/luxfi/mpc/pkg/transport"
|
||||
"github.com/luxfi/mpc/pkg/types"
|
||||
"github.com/luxfi/mpc/pkg/zapauth"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -540,7 +540,7 @@ func runNodeConsensus(ctx context.Context, c *cli.Command) error {
|
||||
// threshold = minimum signers required (operator surface).
|
||||
// Present alongside ready_count so an operator can see how
|
||||
// close to losing quorum the ensemble is.
|
||||
"version": Version,
|
||||
"version": Version,
|
||||
}
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
}
|
||||
@@ -1566,4 +1566,3 @@ func isThresholdLoopback(addr string) bool {
|
||||
}
|
||||
return ip.IsLoopback()
|
||||
}
|
||||
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
// PoA membership (n=1), HTTP control plane, and graceful shutdown.
|
||||
//
|
||||
// What this test covers:
|
||||
// 1. Build mpcd
|
||||
// 2. Start it with --node-id=solo --threshold=1 and no peers
|
||||
// 3. Wait for /healthz to report status=healthy + quorum=true
|
||||
// 4. Confirm the readyCount, threshold, and version fields
|
||||
// 5. Send SIGTERM and confirm clean exit
|
||||
// 1. Build mpcd
|
||||
// 2. Start it with --node-id=solo --threshold=1 and no peers
|
||||
// 3. Wait for /healthz to report status=healthy + quorum=true
|
||||
// 4. Confirm the readyCount, threshold, and version fields
|
||||
// 5. Send SIGTERM and confirm clean exit
|
||||
//
|
||||
// Keygen/signing are NOT exercised here. CGGMP21 and FROST require ≥2
|
||||
// parties to produce a signature share — a 1-of-1 ensemble is structurally
|
||||
|
||||
+14
-10
@@ -56,21 +56,23 @@ type nodeCtx struct {
|
||||
pubKey ed25519.PublicKey
|
||||
privKey ed25519.PrivateKey
|
||||
gate *mpc.SignGate
|
||||
sink interface{ All() []intent.NodeAttestation } // memAttestationSink
|
||||
sink interface {
|
||||
All() []intent.NodeAttestation
|
||||
} // memAttestationSink
|
||||
}
|
||||
|
||||
// clusterRig is the test fixture: 3 MPC nodes, each with a copy of the
|
||||
// same policy bundle + approval keyset, plus 3 executive approval keys.
|
||||
type clusterRig struct {
|
||||
nodes []*nodeCtx
|
||||
signers []signerKey
|
||||
bundle *policy.PolicyBundle
|
||||
nodes []*nodeCtx
|
||||
signers []signerKey
|
||||
bundle *policy.PolicyBundle
|
||||
}
|
||||
|
||||
type signerKey struct {
|
||||
id string
|
||||
pub ed25519.PublicKey
|
||||
priv ed25519.PrivateKey
|
||||
id string
|
||||
pub ed25519.PublicKey
|
||||
priv ed25519.PrivateKey
|
||||
}
|
||||
|
||||
func mkSigner(t *testing.T, id string) signerKey {
|
||||
@@ -411,9 +413,11 @@ func TestPolicyGate_FailureModesAreOrthogonal(t *testing.T) {
|
||||
want: policy.ErrPolicyNotFound,
|
||||
},
|
||||
{
|
||||
name: "expired",
|
||||
mutate: func(_ *clusterRig, ci *intent.CanonicalIntent) { ci.ExpiresAt = time.Date(2026, 4, 27, 11, 0, 0, 0, time.UTC) },
|
||||
want: policy.ErrExpiredIntent,
|
||||
name: "expired",
|
||||
mutate: func(_ *clusterRig, ci *intent.CanonicalIntent) {
|
||||
ci.ExpiresAt = time.Date(2026, 4, 27, 11, 0, 0, 0, time.UTC)
|
||||
},
|
||||
want: policy.ErrExpiredIntent,
|
||||
},
|
||||
{
|
||||
name: "destination off allowlist",
|
||||
|
||||
+2
-2
@@ -72,7 +72,7 @@ func (s *Server) validateJWT(tokenStr string) (*JWTClaims, error) {
|
||||
if claims.Issuer != "mpc.lux.network" {
|
||||
return nil, fmt.Errorf("invalid token issuer")
|
||||
}
|
||||
if !slices.Contains([]string(claims.Audience),"mpc-api") {
|
||||
if !slices.Contains([]string(claims.Audience), "mpc-api") {
|
||||
return nil, fmt.Errorf("invalid token audience")
|
||||
}
|
||||
return claims, nil
|
||||
@@ -95,7 +95,7 @@ func (s *Server) validateRefreshToken(tokenStr string) (*JWTClaims, error) {
|
||||
if claims.Issuer != "mpc.lux.network" {
|
||||
return nil, fmt.Errorf("invalid token issuer")
|
||||
}
|
||||
if !slices.Contains([]string(claims.Audience),"mpc-refresh") {
|
||||
if !slices.Contains([]string(claims.Audience), "mpc-refresh") {
|
||||
return nil, fmt.Errorf("invalid token audience")
|
||||
}
|
||||
return claims, nil
|
||||
|
||||
@@ -38,10 +38,10 @@ type approvalSession struct {
|
||||
CreatedAt time.Time
|
||||
ExpiresAt time.Time
|
||||
|
||||
mu sync.Mutex
|
||||
sigs []approval.ApprovalSignature
|
||||
completed bool
|
||||
rejected bool
|
||||
mu sync.Mutex
|
||||
sigs []approval.ApprovalSignature
|
||||
completed bool
|
||||
rejected bool
|
||||
rejectReason string
|
||||
}
|
||||
|
||||
@@ -393,9 +393,9 @@ func (s *Server) handleApprovalCast(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"status": ifElseString(sess.completed, "approved", "pending"),
|
||||
"collected": len(sess.sigs),
|
||||
"threshold": sess.Threshold,
|
||||
"status": ifElseString(sess.completed, "approved", "pending"),
|
||||
"collected": len(sess.sigs),
|
||||
"threshold": sess.Threshold,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+16
-16
@@ -16,16 +16,16 @@ import (
|
||||
|
||||
// ValidatorKeySet holds MPC wallet references for a validator's BLS and Corona keys.
|
||||
type ValidatorKeySet struct {
|
||||
ValidatorID string `json:"validatorId"`
|
||||
BLSWalletID string `json:"blsWalletId"`
|
||||
ValidatorID string `json:"validatorId"`
|
||||
BLSWalletID string `json:"blsWalletId"`
|
||||
CoronaWalletID string `json:"coronaWalletId"`
|
||||
BLSPublicKey string `json:"blsPublicKey"`
|
||||
BLSPublicKey string `json:"blsPublicKey"`
|
||||
CoronaPublicKey string `json:"coronaPublicKey"`
|
||||
Threshold int `json:"threshold"`
|
||||
Parties int `json:"parties"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
Threshold int `json:"threshold"`
|
||||
Parties int `json:"parties"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type kmsGenerateRequest struct {
|
||||
@@ -109,16 +109,16 @@ func (s *Server) handleKMSGenerate(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
now := time.Now().UTC()
|
||||
ks := &ValidatorKeySet{
|
||||
ValidatorID: req.ValidatorID,
|
||||
BLSWalletID: blsResult.WalletID,
|
||||
ValidatorID: req.ValidatorID,
|
||||
BLSWalletID: blsResult.WalletID,
|
||||
CoronaWalletID: coronaResult.WalletID,
|
||||
BLSPublicKey: blsResult.ECDSAPubKey,
|
||||
BLSPublicKey: blsResult.ECDSAPubKey,
|
||||
CoronaPublicKey: coronaResult.EDDSAPubKey,
|
||||
Threshold: req.Threshold,
|
||||
Parties: req.Parties,
|
||||
Status: "active",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
Threshold: req.Threshold,
|
||||
Parties: req.Parties,
|
||||
Status: "active",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
if s.db != nil {
|
||||
|
||||
@@ -629,13 +629,13 @@ func (s *Server) handleBiometricEnroll(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
var req struct {
|
||||
CredentialID string `json:"credentialId"`
|
||||
SignedResponse string `json:"signedResponse"`
|
||||
AuthenticatorData string `json:"authenticatorData"`
|
||||
ClientDataJSON string `json:"clientDataJSON"`
|
||||
PublicKey string `json:"publicKey"`
|
||||
Modality string `json:"modality"`
|
||||
LivenessAttestation string `json:"livenessAttestation"` // base64 envelope
|
||||
CredentialID string `json:"credentialId"`
|
||||
SignedResponse string `json:"signedResponse"`
|
||||
AuthenticatorData string `json:"authenticatorData"`
|
||||
ClientDataJSON string `json:"clientDataJSON"`
|
||||
PublicKey string `json:"publicKey"`
|
||||
Modality string `json:"modality"`
|
||||
LivenessAttestation string `json:"livenessAttestation"` // base64 envelope
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "invalid request body")
|
||||
|
||||
@@ -26,15 +26,15 @@ var errSessionConflict = errors.New("session modified concurrently")
|
||||
|
||||
// sessionResponse is the JSON shape specified in mpc.yaml.
|
||||
type sessionResponse struct {
|
||||
SessionID string `json:"sessionId"`
|
||||
WalletID string `json:"walletId"`
|
||||
GrantedTo string `json:"grantedTo,omitempty"`
|
||||
Scopes []string `json:"scopes,omitempty"`
|
||||
ValueLimit *string `json:"valueLimit,omitempty"`
|
||||
OperationLimit *int `json:"operationLimit,omitempty"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
ExpiresAt time.Time `json:"expiresAt"`
|
||||
SessionID string `json:"sessionId"`
|
||||
WalletID string `json:"walletId"`
|
||||
GrantedTo string `json:"grantedTo,omitempty"`
|
||||
Scopes []string `json:"scopes,omitempty"`
|
||||
ValueLimit *string `json:"valueLimit,omitempty"`
|
||||
OperationLimit *int `json:"operationLimit,omitempty"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
ExpiresAt time.Time `json:"expiresAt"`
|
||||
RevokedAt *time.Time `json:"revokedAt,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -272,8 +272,8 @@ func (s *Server) handleCreateWalletBackup(w http.ResponseWriter, r *http.Request
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Threshold int `json:"threshold"`
|
||||
TotalShards int `json:"total_shards"`
|
||||
Threshold int `json:"threshold"`
|
||||
TotalShards int `json:"total_shards"`
|
||||
Destinations []string `json:"destinations"`
|
||||
UserWebAuthnPubKey string `json:"user_webauthn_pub_key,omitempty"` // P-256 public key for passkey-encrypted backup
|
||||
}
|
||||
@@ -419,7 +419,7 @@ func (s *Server) handleCreateWalletBackup(w http.ResponseWriter, r *http.Request
|
||||
backup.TotalShards = req.TotalShards
|
||||
backup.Status = "active"
|
||||
backup.UserPasskeyFingerprint = userPasskeyPubKey
|
||||
backup.EncryptedKeyShare = encrypted // AES-256-GCM ciphertext, decryptable ONLY with user's passkey
|
||||
backup.EncryptedKeyShare = encrypted // AES-256-GCM ciphertext, decryptable ONLY with user's passkey
|
||||
backup.EphemeralPublicKey = base64.StdEncoding.EncodeToString(ephPub.Bytes()) // needed for user-side ECDH
|
||||
|
||||
if err := backup.Create(); err != nil {
|
||||
|
||||
@@ -58,16 +58,16 @@ const treasurySignPermission = "mpc.treasury.sign"
|
||||
// --- Request / response shapes ---
|
||||
|
||||
type treasuryWalletResponse struct {
|
||||
WalletID string `json:"walletId"`
|
||||
Name string `json:"name"`
|
||||
Address string `json:"address,omitempty"`
|
||||
Chain string `json:"chain"`
|
||||
Signers []treasurySignerView `json:"signers"`
|
||||
Tiers []db.TreasuryTier `json:"tiers"`
|
||||
RegulatorShard bool `json:"regulatorShard"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Outstanding []outstandingOpView `json:"outstanding,omitempty"`
|
||||
WalletID string `json:"walletId"`
|
||||
Name string `json:"name"`
|
||||
Address string `json:"address,omitempty"`
|
||||
Chain string `json:"chain"`
|
||||
Signers []treasurySignerView `json:"signers"`
|
||||
Tiers []db.TreasuryTier `json:"tiers"`
|
||||
RegulatorShard bool `json:"regulatorShard"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Outstanding []outstandingOpView `json:"outstanding,omitempty"`
|
||||
}
|
||||
|
||||
type treasurySignerView struct {
|
||||
|
||||
+36
-35
@@ -78,18 +78,18 @@ type tieredWalletCreateRequest struct {
|
||||
}
|
||||
|
||||
type tieredWalletResponse struct {
|
||||
ID string `json:"id"`
|
||||
OrgID string `json:"orgId"`
|
||||
Tier string `json:"tier"`
|
||||
Chain string `json:"chain"`
|
||||
Address string `json:"address"`
|
||||
GroupPubKey string `json:"groupPubKey,omitempty"` // hex
|
||||
Threshold wallet.ThresholdSpec `json:"threshold"`
|
||||
Nodes []wallet.NodeBinding `json:"nodes"`
|
||||
Policy wallet.TierPolicy `json:"policy"`
|
||||
PolicyID string `json:"policyId,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
ID string `json:"id"`
|
||||
OrgID string `json:"orgId"`
|
||||
Tier string `json:"tier"`
|
||||
Chain string `json:"chain"`
|
||||
Address string `json:"address"`
|
||||
GroupPubKey string `json:"groupPubKey,omitempty"` // hex
|
||||
Threshold wallet.ThresholdSpec `json:"threshold"`
|
||||
Nodes []wallet.NodeBinding `json:"nodes"`
|
||||
Policy wallet.TierPolicy `json:"policy"`
|
||||
PolicyID string `json:"policyId,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
func toTieredWalletResponse(w wallet.Wallet) tieredWalletResponse {
|
||||
@@ -226,16 +226,17 @@ func (s *Server) handleTieredWalletListByTier(w http.ResponseWriter, r *http.Req
|
||||
|
||||
// signDecision represents the outcome of the tier-aware sign dispatch.
|
||||
// Status: "auto_approved" | "approval_required" | "airgapped_queued" |
|
||||
// "timelocked" | "rejected"
|
||||
//
|
||||
// "timelocked" | "rejected"
|
||||
type signDecision struct {
|
||||
Status string `json:"status"`
|
||||
WalletID string `json:"walletId"`
|
||||
Tier string `json:"tier"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
RequiredApprovals int `json:"requiredApprovals,omitempty"`
|
||||
TimelockEndsAt *time.Time `json:"timelockEndsAt,omitempty"`
|
||||
Usage *wallet.UsageView `json:"usage,omitempty"`
|
||||
NextStep string `json:"nextStep,omitempty"`
|
||||
Status string `json:"status"`
|
||||
WalletID string `json:"walletId"`
|
||||
Tier string `json:"tier"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
RequiredApprovals int `json:"requiredApprovals,omitempty"`
|
||||
TimelockEndsAt *time.Time `json:"timelockEndsAt,omitempty"`
|
||||
Usage *wallet.UsageView `json:"usage,omitempty"`
|
||||
NextStep string `json:"nextStep,omitempty"`
|
||||
}
|
||||
|
||||
type tieredSignRequest struct {
|
||||
@@ -250,20 +251,20 @@ type tieredSignRequest struct {
|
||||
//
|
||||
// The decision tree:
|
||||
//
|
||||
// 1. Quarantine: ALWAYS reject. No path to a signature.
|
||||
// 2. Per-tx + daily + velocity limits: enforced first; over-cap rejects.
|
||||
// 3. Airgapped tiers (cold, DR): caller must drive the airgap flow
|
||||
// (pkg/airgap/session). Returns "airgapped_queued" with the next
|
||||
// step. Sibling task #106 finalizes the producer hookup.
|
||||
// 4. Timelocked tiers (cold 24h, contract_admin 48h, DR 7d, quarantine
|
||||
// 72h): returns "timelocked" with TimelockEndsAt; the broadcast
|
||||
// side picks it up after expiry. Sibling task ships the queue.
|
||||
// 5. AutoApproval tiers (hot/gas/bridge/validator): allowlist check +
|
||||
// amount under per-tx limit AND not over the large-amount escalation
|
||||
// threshold → "auto_approved", counters are committed.
|
||||
// 6. Otherwise: requires HumanApprovalsMin (or HumanApprovalsLarge if
|
||||
// amount >= LargeAmount). Returns "approval_required". Sibling task
|
||||
// ships the approval bundle verifier.
|
||||
// 1. Quarantine: ALWAYS reject. No path to a signature.
|
||||
// 2. Per-tx + daily + velocity limits: enforced first; over-cap rejects.
|
||||
// 3. Airgapped tiers (cold, DR): caller must drive the airgap flow
|
||||
// (pkg/airgap/session). Returns "airgapped_queued" with the next
|
||||
// step. Sibling task #106 finalizes the producer hookup.
|
||||
// 4. Timelocked tiers (cold 24h, contract_admin 48h, DR 7d, quarantine
|
||||
// 72h): returns "timelocked" with TimelockEndsAt; the broadcast
|
||||
// side picks it up after expiry. Sibling task ships the queue.
|
||||
// 5. AutoApproval tiers (hot/gas/bridge/validator): allowlist check +
|
||||
// amount under per-tx limit AND not over the large-amount escalation
|
||||
// threshold → "auto_approved", counters are committed.
|
||||
// 6. Otherwise: requires HumanApprovalsMin (or HumanApprovalsLarge if
|
||||
// amount >= LargeAmount). Returns "approval_required". Sibling task
|
||||
// ships the approval bundle verifier.
|
||||
//
|
||||
// KYT and CanonicalIntent verifier interface are referenced via the
|
||||
// sibling LocalVerifier task — for now we surface the requirement in the
|
||||
|
||||
@@ -77,9 +77,9 @@ func TestCrossTenant_OrgIDSanitization(t *testing.T) {
|
||||
input string
|
||||
contains string // must NOT appear in the scoped key
|
||||
}{
|
||||
{"a:b:c", "org:a:b:c:"}, // triple colon injection
|
||||
{"a:b:c", "org:a:b:c:"}, // triple colon injection
|
||||
{"../etc/passwd", "org:../etc/passwd"}, // path traversal (colons don't apply but still test)
|
||||
{"org\x00evil", "org:org\x00evil:"}, // null byte
|
||||
{"org\x00evil", "org:org\x00evil:"}, // null byte
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
@@ -508,4 +508,3 @@ func bytes_repeat(b byte, n int) []byte {
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
|
||||
@@ -72,9 +72,9 @@ func TestBackupShardRequiresPasskey(t *testing.T) {
|
||||
func TestBackupShardRejectsEmptyPasskey(t *testing.T) {
|
||||
// Empty or invalid passkey pubkeys must be rejected.
|
||||
invalidKeys := []string{
|
||||
"", // empty
|
||||
"not-base64!@#$", // garbage
|
||||
base64.StdEncoding.EncodeToString([]byte{0x04}), // too short
|
||||
"", // empty
|
||||
"not-base64!@#$", // garbage
|
||||
base64.StdEncoding.EncodeToString([]byte{0x04}), // too short
|
||||
base64.StdEncoding.EncodeToString(make([]byte, 65)), // starts with 0x00, not 0x04
|
||||
}
|
||||
for _, key := range invalidKeys {
|
||||
|
||||
+10
-10
@@ -68,14 +68,14 @@ type HSMProvider interface {
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
db *db.Database
|
||||
mpc MPCBackend
|
||||
hsm HSMProvider // optional: server-side HSM co-signing
|
||||
tradeApproval *custody.TradeApprovalService
|
||||
txTracker *txtracker.Tracker
|
||||
jwtSecret []byte
|
||||
oidcIssuers []string
|
||||
webauthnRPID string
|
||||
db *db.Database
|
||||
mpc MPCBackend
|
||||
hsm HSMProvider // optional: server-side HSM co-signing
|
||||
tradeApproval *custody.TradeApprovalService
|
||||
txTracker *txtracker.Tracker
|
||||
jwtSecret []byte
|
||||
oidcIssuers []string
|
||||
webauthnRPID string
|
||||
webauthnOrigins map[string]bool
|
||||
// PubKey is the Ed25519 verifying key used to validate
|
||||
// (or any other configured PAD-2 liveness provider) attestations
|
||||
@@ -88,8 +88,8 @@ type Server struct {
|
||||
// intended only for the extended-envelope rollout window.
|
||||
// Configured via MPC_LIVENESS_BINDING=strict|lax (default strict).
|
||||
livenessBindingMode webauthn.BindingMode
|
||||
router chi.Router
|
||||
replayGuard *replayGuard
|
||||
router chi.Router
|
||||
replayGuard *replayGuard
|
||||
|
||||
// approval is the executive-approval registry consumed by /v1/approval/*.
|
||||
// Optional — when nil, those endpoints return 503. Wired via SetApproval.
|
||||
|
||||
@@ -146,7 +146,7 @@ func TestKMSZAP_Auth_RequiredHappyPath(t *testing.T) {
|
||||
|
||||
// Now a sign call should succeed.
|
||||
respOp, signBody := callOp(t, client, peer, OpKMSSign, kmsZapSignRequest{
|
||||
VaultID: "v1", WalletID: "w1", Payload: []byte("x"),
|
||||
VaultID: "v1", WalletID: "w1", Payload: []byte("x"),
|
||||
})
|
||||
if respOp != OpKMSSign {
|
||||
t.Fatalf("sign opcode: got 0x%04x want 0x%04x", respOp, OpKMSSign)
|
||||
@@ -171,7 +171,7 @@ func TestKMSZAP_Auth_RequiredRejectsMissing(t *testing.T) {
|
||||
defer client.Stop()
|
||||
|
||||
op, body := callOp(t, client, peer, OpKMSSign, kmsZapSignRequest{
|
||||
VaultID: "v1", WalletID: "w1", Payload: []byte("x"),
|
||||
VaultID: "v1", WalletID: "w1", Payload: []byte("x"),
|
||||
})
|
||||
if op != OpKMSSign {
|
||||
t.Fatalf("opcode echo: got 0x%04x want 0x%04x", op, OpKMSSign)
|
||||
@@ -204,7 +204,7 @@ func TestKMSZAP_Auth_RequiredRejectsBadToken(t *testing.T) {
|
||||
}
|
||||
|
||||
op, signBody := callOp(t, client, peer, OpKMSSign, kmsZapSignRequest{
|
||||
VaultID: "v1", WalletID: "w1", Payload: []byte("x"),
|
||||
VaultID: "v1", WalletID: "w1", Payload: []byte("x"),
|
||||
})
|
||||
if op != OpKMSSign {
|
||||
t.Fatalf("opcode echo: got 0x%04x want 0x%04x", op, OpKMSSign)
|
||||
@@ -225,7 +225,7 @@ func TestKMSZAP_Auth_AdvisoryAcceptsMissing(t *testing.T) {
|
||||
defer client.Stop()
|
||||
|
||||
op, body := callOp(t, client, peer, OpKMSSign, kmsZapSignRequest{
|
||||
VaultID: "v1", WalletID: "w1", Payload: []byte("x"),
|
||||
VaultID: "v1", WalletID: "w1", Payload: []byte("x"),
|
||||
})
|
||||
if op != OpKMSSign {
|
||||
t.Fatalf("opcode echo: got 0x%04x want 0x%04x", op, OpKMSSign)
|
||||
|
||||
+11
-11
@@ -13,17 +13,17 @@ import (
|
||||
//
|
||||
// Supported provider types:
|
||||
//
|
||||
// ledger-enterprise — Ledger Enterprise (Vault) REST, ECDSA-P256 attestations
|
||||
// ledger-device — USB Ledger Nano via APDU, secp256k1 / Ed25519
|
||||
// webauthn — FIDO2 / passkey, ES256
|
||||
// aws-kms — AWS KMS asymmetric ECDSA via luxfi/hsm
|
||||
// gcp-kms — Google Cloud KMS via luxfi/hsm
|
||||
// azure-keyvault — Azure Key Vault via luxfi/hsm
|
||||
// zymbit — Zymbit SCM via luxfi/hsm
|
||||
// yubihsm — Yubico YubiHSM 2 via luxfi/hsm
|
||||
// mldsa — Post-quantum ML-DSA-65 via luxfi/hsm
|
||||
// safe-multisig — Gnosis Safe execTransaction, on-chain approval
|
||||
// local-dev — Software Ed25519 (test only; refuses production)
|
||||
// ledger-enterprise — Ledger Enterprise (Vault) REST, ECDSA-P256 attestations
|
||||
// ledger-device — USB Ledger Nano via APDU, secp256k1 / Ed25519
|
||||
// webauthn — FIDO2 / passkey, ES256
|
||||
// aws-kms — AWS KMS asymmetric ECDSA via luxfi/hsm
|
||||
// gcp-kms — Google Cloud KMS via luxfi/hsm
|
||||
// azure-keyvault — Azure Key Vault via luxfi/hsm
|
||||
// zymbit — Zymbit SCM via luxfi/hsm
|
||||
// yubihsm — Yubico YubiHSM 2 via luxfi/hsm
|
||||
// mldsa — Post-quantum ML-DSA-65 via luxfi/hsm
|
||||
// safe-multisig — Gnosis Safe execTransaction, on-chain approval
|
||||
// local-dev — Software Ed25519 (test only; refuses production)
|
||||
//
|
||||
// Production safety: providers that hold a private key only acceptable in
|
||||
// non-prod environments (currently only "local-dev") refuse to start when
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
//
|
||||
// Configuration:
|
||||
//
|
||||
// region — AWS region (e.g. "us-east-1"). Defaults to AWS_REGION env.
|
||||
// region — AWS region (e.g. "us-east-1"). Defaults to AWS_REGION env.
|
||||
//
|
||||
// Approver enrollment is explicit: callers must register each approver's
|
||||
// (approverID -> KMS key ARN, P-256 public key, optional attestation)
|
||||
|
||||
@@ -20,8 +20,8 @@ import (
|
||||
//
|
||||
// Configuration:
|
||||
//
|
||||
// vault_url — full Vault URL (e.g. "https://approvals.vault.azure.net").
|
||||
// Optional; falls back to AZURE_VAULT_URL.
|
||||
// vault_url — full Vault URL (e.g. "https://approvals.vault.azure.net").
|
||||
// Optional; falls back to AZURE_VAULT_URL.
|
||||
//
|
||||
// Approver enrollment by Vault key name + cached SPKI public key.
|
||||
type AzureKeyVaultProvider struct {
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
// GCPKMSProvider produces ECDSA-P256 approval signatures via Google Cloud
|
||||
// KMS asymmetric keys. Mirrors AWSKMSProvider; key resource name format:
|
||||
//
|
||||
// projects/{project}/locations/{location}/keyRings/{keyRing}/cryptoKeys/{key}/cryptoKeyVersions/{version}
|
||||
// projects/{project}/locations/{location}/keyRings/{keyRing}/cryptoKeys/{key}/cryptoKeyVersions/{version}
|
||||
type GCPKMSProvider struct {
|
||||
signer luxhsm.Signer
|
||||
|
||||
|
||||
@@ -36,18 +36,18 @@ import (
|
||||
//
|
||||
// Configuration:
|
||||
//
|
||||
// ledgerctl_path — path to ledgerctl (default "ledgerctl" on PATH)
|
||||
// app — Ledger app name. Default "ethereum". Set to
|
||||
// "lux" for Lux native paths, "bitcoin" for BTC, etc.
|
||||
// bip32_path — derivation path. Default m/44'/9000'/0'/0/0 (Lux
|
||||
// native). For EVM-compat use m/44'/60'/0'/0/0;
|
||||
// ledger.BIP44PathForName(...) builds this.
|
||||
// chain_id — EIP-155 chain id forwarded to ledgerctl when app
|
||||
// is "ethereum". Optional.
|
||||
// sign_action — override the verb passed to ledgerctl; defaults
|
||||
// to "{app}-sign-hash".
|
||||
// pubkey_action — override the verb for fetching the device pubkey;
|
||||
// defaults to "{app}-get-pubkey".
|
||||
// ledgerctl_path — path to ledgerctl (default "ledgerctl" on PATH)
|
||||
// app — Ledger app name. Default "ethereum". Set to
|
||||
// "lux" for Lux native paths, "bitcoin" for BTC, etc.
|
||||
// bip32_path — derivation path. Default m/44'/9000'/0'/0/0 (Lux
|
||||
// native). For EVM-compat use m/44'/60'/0'/0/0;
|
||||
// ledger.BIP44PathForName(...) builds this.
|
||||
// chain_id — EIP-155 chain id forwarded to ledgerctl when app
|
||||
// is "ethereum". Optional.
|
||||
// sign_action — override the verb passed to ledgerctl; defaults
|
||||
// to "{app}-sign-hash".
|
||||
// pubkey_action — override the verb for fetching the device pubkey;
|
||||
// defaults to "{app}-get-pubkey".
|
||||
//
|
||||
// Verification: pubkeys returned by the device are parsed as compressed
|
||||
// SEC1 secp256k1 (33 bytes). Approvals sign SHA-256(intent.Bytes())
|
||||
@@ -377,4 +377,3 @@ func asn1MarshalRS(r, s *big.Int) ([]byte, error) {
|
||||
// Reuse the helper from ecdsa_helpers.go via the local ecdsaSig type.
|
||||
return marshalECDSASig(ecdsaSig{R: r, S: s})
|
||||
}
|
||||
|
||||
|
||||
@@ -26,18 +26,18 @@ import (
|
||||
//
|
||||
// Ledger Enterprise REST endpoints used:
|
||||
//
|
||||
// POST /accounts/{accountId}/transactions — propose tx
|
||||
// GET /accounts/{accountId}/transactions/{txId} — poll status
|
||||
// POST /accounts/{accountId}/transactions/{txId}/approve — approve via PSD
|
||||
// POST /accounts/{accountId}/transactions — propose tx
|
||||
// GET /accounts/{accountId}/transactions/{txId} — poll status
|
||||
// POST /accounts/{accountId}/transactions/{txId}/approve — approve via PSD
|
||||
//
|
||||
// Public docs: https://docs.ledger.com/enterprise
|
||||
//
|
||||
// Configuration:
|
||||
//
|
||||
// endpoint — Vault REST base URL (e.g. "https://vault.ledger-enterprise.example/api")
|
||||
// api_key — service-account API key (Bearer token)
|
||||
// workspace — workspace ID
|
||||
// timeout — HTTP request timeout (default 30s); approval polling timeout (default 30m)
|
||||
// endpoint — Vault REST base URL (e.g. "https://vault.ledger-enterprise.example/api")
|
||||
// api_key — service-account API key (Bearer token)
|
||||
// workspace — workspace ID
|
||||
// timeout — HTTP request timeout (default 30s); approval polling timeout (default 30m)
|
||||
//
|
||||
// Approver enrollment maps approverID -> Vault account ID + cached secp256k1
|
||||
// public key (Ledger Enterprise typically uses secp256k1 for transaction
|
||||
@@ -169,14 +169,14 @@ func (p *LedgerEnterpriseProvider) GetPublicIdentity(_ context.Context, approver
|
||||
|
||||
type leTransactionRequest struct {
|
||||
Workspace string `json:"workspace"`
|
||||
Type string `json:"type"` // "approval-attestation"
|
||||
Digest string `json:"digest"` // base64 of intent digest
|
||||
Type string `json:"type"` // "approval-attestation"
|
||||
Digest string `json:"digest"` // base64 of intent digest
|
||||
Algorithm string `json:"signing_algorithm"`
|
||||
}
|
||||
|
||||
type leTransactionResponse struct {
|
||||
TxID string `json:"transaction_id"`
|
||||
Status string `json:"status"` // "pending", "approved", "rejected", "failed"
|
||||
Status string `json:"status"` // "pending", "approved", "rejected", "failed"
|
||||
Sig string `json:"signature,omitempty"` // base64 DER signature when approved
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ type MLDSAProvider struct {
|
||||
}
|
||||
|
||||
type mldsaEntry struct {
|
||||
PrivKey *mldsa.PrivateKey
|
||||
PubKey *mldsa.PublicKey
|
||||
PrivKey *mldsa.PrivateKey
|
||||
PubKey *mldsa.PublicKey
|
||||
PubBytes []byte
|
||||
}
|
||||
|
||||
|
||||
@@ -16,13 +16,13 @@ import (
|
||||
//
|
||||
// Layout of an approval bundle for this provider:
|
||||
//
|
||||
// [chainID (8B BE)] [safeAddress (20B)] [safeTxHash (32B)] [execTxHash (32B)]
|
||||
// [chainID (8B BE)] [safeAddress (20B)] [safeTxHash (32B)] [execTxHash (32B)]
|
||||
//
|
||||
// Verification: a service caller checks
|
||||
//
|
||||
// 1. safeTxHash == Safe.getTransactionHash(intent.Bytes() / EIP-712 typed data)
|
||||
// 2. execTxHash is mined (≥ 1 confirmation) on the chain
|
||||
// 3. execTxHash receipt logs include `ExecutionSuccess(safeTxHash)`
|
||||
// 1. safeTxHash == Safe.getTransactionHash(intent.Bytes() / EIP-712 typed data)
|
||||
// 2. execTxHash is mined (≥ 1 confirmation) on the chain
|
||||
// 3. execTxHash receipt logs include `ExecutionSuccess(safeTxHash)`
|
||||
//
|
||||
// This package only validates the bundle's *shape* — on-chain confirmation
|
||||
// is the caller's responsibility (it requires an EVM RPC client we don't
|
||||
@@ -30,8 +30,8 @@ import (
|
||||
//
|
||||
// Configuration:
|
||||
//
|
||||
// chain_id — decimal chain ID (e.g. "1" mainnet, "42161" arbitrum)
|
||||
// safe_address — 0x-prefixed Safe address (20 bytes)
|
||||
// chain_id — decimal chain ID (e.g. "1" mainnet, "42161" arbitrum)
|
||||
// safe_address — 0x-prefixed Safe address (20 bytes)
|
||||
//
|
||||
// Approver enrollment: register approverID -> Safe address. One Safe can
|
||||
// represent any number of logical approvers (each with their own owner key)
|
||||
@@ -52,9 +52,9 @@ type safeEntry struct {
|
||||
}
|
||||
|
||||
type pendingSafe struct {
|
||||
intent CanonicalIntent
|
||||
expiresAt time.Time
|
||||
resultCh chan safeResult
|
||||
intent CanonicalIntent
|
||||
expiresAt time.Time
|
||||
resultCh chan safeResult
|
||||
}
|
||||
|
||||
type safeResult struct {
|
||||
|
||||
@@ -28,10 +28,10 @@ import (
|
||||
//
|
||||
// Configuration:
|
||||
//
|
||||
// rpid — Relying Party ID (e.g. "approvals.fund.com"). Required.
|
||||
// origins — comma-separated allowed origins (e.g. "https://approvals.fund.com").
|
||||
// In production, MUST be HTTPS.
|
||||
// timeout — challenge TTL (default 5m). Override with "300s" / "5m".
|
||||
// rpid — Relying Party ID (e.g. "approvals.fund.com"). Required.
|
||||
// origins — comma-separated allowed origins (e.g. "https://approvals.fund.com").
|
||||
// In production, MUST be HTTPS.
|
||||
// timeout — challenge TTL (default 5m). Override with "300s" / "5m".
|
||||
//
|
||||
// HTTPS enforcement: NewWebAuthn requires HTTPS origins unless MPC_ENV=dev.
|
||||
type WebAuthnProvider struct {
|
||||
@@ -39,22 +39,22 @@ type WebAuthnProvider struct {
|
||||
allowedOrigins map[string]bool
|
||||
timeout time.Duration
|
||||
|
||||
mu sync.Mutex
|
||||
identity map[string]webauthnEntry
|
||||
pending map[string]*pendingWebAuthn
|
||||
mu sync.Mutex
|
||||
identity map[string]webauthnEntry
|
||||
pending map[string]*pendingWebAuthn
|
||||
}
|
||||
|
||||
type webauthnEntry struct {
|
||||
CredentialID string // base64url-encoded WebAuthn credential ID
|
||||
PubKey *ecdsa.PublicKey
|
||||
CredentialID string // base64url-encoded WebAuthn credential ID
|
||||
PubKey *ecdsa.PublicKey
|
||||
PubKeyUncompressed []byte // 65-byte 0x04||X||Y
|
||||
}
|
||||
|
||||
type pendingWebAuthn struct {
|
||||
intent CanonicalIntent
|
||||
challenge []byte // 32 random bytes; binds to intent.Digest()
|
||||
expiresAt time.Time
|
||||
resultCh chan webauthnResult
|
||||
intent CanonicalIntent
|
||||
challenge []byte // 32 random bytes; binds to intent.Digest()
|
||||
expiresAt time.Time
|
||||
resultCh chan webauthnResult
|
||||
}
|
||||
|
||||
type webauthnResult struct {
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
//
|
||||
// Configuration:
|
||||
//
|
||||
// api_addr — Zymbit REST API URL (default "http://localhost:6789").
|
||||
// api_addr — Zymbit REST API URL (default "http://localhost:6789").
|
||||
//
|
||||
// Approver enrollment maps approverID -> Zymbit slot ID + cached SPKI public
|
||||
// key. Slots must be provisioned out-of-band on the Zymbit device.
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
//
|
||||
// One way to wire approval:
|
||||
//
|
||||
// intent := intent.New(...)
|
||||
// bundle, err := orchestrator.CollectApprovals(ctx, intent, approvers, threshold)
|
||||
// // intent.SetApprovals(bundle); MPC signing only proceeds when
|
||||
// // bundle has >= threshold valid signatures from required approvers.
|
||||
// intent := intent.New(...)
|
||||
// bundle, err := orchestrator.CollectApprovals(ctx, intent, approvers, threshold)
|
||||
// // intent.SetApprovals(bundle); MPC signing only proceeds when
|
||||
// // bundle has >= threshold valid signatures from required approvers.
|
||||
package approval
|
||||
|
||||
import (
|
||||
@@ -103,10 +103,10 @@ type ApprovalSignature struct {
|
||||
// Use the named constants — never raw strings — so a typo at the call site
|
||||
// becomes a compile error.
|
||||
const (
|
||||
AlgorithmECDSAP256 = "ECDSA-P256" // generic ECDSA secp256r1, SHA-256
|
||||
AlgorithmECDSAsecp256k1 = "ECDSA-secp256k1" // Ethereum / Bitcoin signatures
|
||||
AlgorithmECDSAP256 = "ECDSA-P256" // generic ECDSA secp256r1, SHA-256
|
||||
AlgorithmECDSAsecp256k1 = "ECDSA-secp256k1" // Ethereum / Bitcoin signatures
|
||||
AlgorithmEd25519 = "Ed25519"
|
||||
AlgorithmMLDSA65 = "ML-DSA-65" // FIPS 204, NIST level 3
|
||||
AlgorithmMLDSA65 = "ML-DSA-65" // FIPS 204, NIST level 3
|
||||
AlgorithmSafeContract = "Safe-execTransaction" // on-chain Safe approval
|
||||
AlgorithmWebAuthnES256 = "WebAuthn-ES256" // CTAP2 ES256 (P-256)
|
||||
AlgorithmWebAuthnES256 = "WebAuthn-ES256" // CTAP2 ES256 (P-256)
|
||||
)
|
||||
|
||||
@@ -61,28 +61,28 @@ func (t TEEKind) String() string {
|
||||
// not retained here; this struct holds only the post-verification
|
||||
// projection that downstream gates need.
|
||||
type CPUQuote struct {
|
||||
Kind TEEKind // SEV-SNP or TDX
|
||||
Verified bool // verifier confirmed the signature chain
|
||||
MeasurementHash [32]byte // launch measurement (MRTD or LAUNCH_MEASUREMENT)
|
||||
ReportData [32]byte // freshness binding (e.g. nonce hash)
|
||||
VerifiedAt time.Time // when the verifier signed off
|
||||
VendorCertChain [][]byte // AMD or Intel vendor cert chain (DER)
|
||||
Kind TEEKind // SEV-SNP or TDX
|
||||
Verified bool // verifier confirmed the signature chain
|
||||
MeasurementHash [32]byte // launch measurement (MRTD or LAUNCH_MEASUREMENT)
|
||||
ReportData [32]byte // freshness binding (e.g. nonce hash)
|
||||
VerifiedAt time.Time // when the verifier signed off
|
||||
VendorCertChain [][]byte // AMD or Intel vendor cert chain (DER)
|
||||
}
|
||||
|
||||
// GPUEvidence is the verified output of an NVIDIA GPU attestation.
|
||||
// Populated by GPUReport.Parse + NRASClient.Verify + RIM.Match.
|
||||
type GPUEvidence struct {
|
||||
UUID string // GPU UUID, e.g. "GPU-1234..."
|
||||
DriverVersion string // e.g. "535.104.05"
|
||||
VBIOSVersion string // e.g. "96.00.74.00.01"
|
||||
Architecture string // "Hopper" | "Blackwell"
|
||||
Verified bool // NRAS returned a valid token
|
||||
NRASTokenHash [32]byte // sha256 of the NRAS token bytes
|
||||
MeasurementRoot [32]byte // root over the measurement list
|
||||
RIMMatched bool // every measurement matched the RIM
|
||||
Nonce [32]byte // freshness nonce echoed in report
|
||||
VerifiedAt time.Time // when NRAS signed the token
|
||||
NVSwitchVerified bool // multi-GPU NVSwitch attested (optional)
|
||||
UUID string // GPU UUID, e.g. "GPU-1234..."
|
||||
DriverVersion string // e.g. "535.104.05"
|
||||
VBIOSVersion string // e.g. "96.00.74.00.01"
|
||||
Architecture string // "Hopper" | "Blackwell"
|
||||
Verified bool // NRAS returned a valid token
|
||||
NRASTokenHash [32]byte // sha256 of the NRAS token bytes
|
||||
MeasurementRoot [32]byte // root over the measurement list
|
||||
RIMMatched bool // every measurement matched the RIM
|
||||
Nonce [32]byte // freshness nonce echoed in report
|
||||
VerifiedAt time.Time // when NRAS signed the token
|
||||
NVSwitchVerified bool // multi-GPU NVSwitch attested (optional)
|
||||
}
|
||||
|
||||
// VerifiedBundle binds a CPU TEE quote to a GPU evidence record
|
||||
@@ -111,11 +111,11 @@ type VerifiedResult struct {
|
||||
|
||||
// Errors returned by Verify, in the order they are checked.
|
||||
var (
|
||||
ErrBundleMissingWorker = errors.New("nvidia: composite missing worker ID")
|
||||
ErrBundleNoCPUQuote = errors.New("nvidia: composite missing CPU quote")
|
||||
ErrBundleBadCPU = errors.New("nvidia: CPU quote not verified")
|
||||
ErrBundleBadGPU = errors.New("nvidia: GPU evidence not verified")
|
||||
ErrBundleRIMMismatch = errors.New("nvidia: RIM mismatch on GPU measurements")
|
||||
ErrBundleMissingWorker = errors.New("nvidia: composite missing worker ID")
|
||||
ErrBundleNoCPUQuote = errors.New("nvidia: composite missing CPU quote")
|
||||
ErrBundleBadCPU = errors.New("nvidia: CPU quote not verified")
|
||||
ErrBundleBadGPU = errors.New("nvidia: GPU evidence not verified")
|
||||
ErrBundleRIMMismatch = errors.New("nvidia: RIM mismatch on GPU measurements")
|
||||
ErrBundleBindingMissing = errors.New("nvidia: CPU report_data does not bind GPU nonce")
|
||||
ErrBundleStale = errors.New("nvidia: composite older than freshness window")
|
||||
)
|
||||
@@ -179,7 +179,8 @@ func (c *VerifiedBundle) Verify(now time.Time, freshness time.Duration) (Verifie
|
||||
// use this when constructing the CPU quote so the composite verifies.
|
||||
//
|
||||
// Layout: sha256( "luxfi/mpc/composite\x00" || workerID || "\x00" ||
|
||||
// gpuUUID || "\x00" || gpuNonce ).
|
||||
//
|
||||
// gpuUUID || "\x00" || gpuNonce ).
|
||||
//
|
||||
// The leading domain string + null separators prevent cross-domain
|
||||
// reuse of the same hash (a SEV-SNP report bound this way cannot be
|
||||
|
||||
@@ -136,9 +136,9 @@ func TestBindCPUReportData_StableAcrossInputs(t *testing.T) {
|
||||
|
||||
func TestTEEKind_String(t *testing.T) {
|
||||
cases := map[TEEKind]string{
|
||||
TEENone: "none",
|
||||
TEESEVSNP: "sev_snp",
|
||||
TEETDX: "tdx",
|
||||
TEENone: "none",
|
||||
TEESEVSNP: "sev_snp",
|
||||
TEETDX: "tdx",
|
||||
TEEKind(99): "invalid",
|
||||
}
|
||||
for k, want := range cases {
|
||||
|
||||
@@ -20,13 +20,13 @@ import (
|
||||
|
||||
// Errors returned by the parser.
|
||||
var (
|
||||
ErrReportEmpty = errors.New("nvidia/report: empty input")
|
||||
ErrReportBadJSON = errors.New("nvidia/report: bad JSON envelope")
|
||||
ErrReportBadVersion = errors.New("nvidia/report: unsupported evidence_version")
|
||||
ErrReportMissingNonce = errors.New("nvidia/report: missing nonce")
|
||||
ErrReportBadNonce = errors.New("nvidia/report: nonce must be 32 bytes hex")
|
||||
ErrReportMissingArch = errors.New("nvidia/report: missing architecture")
|
||||
ErrReportUnknownArch = errors.New("nvidia/report: unknown architecture")
|
||||
ErrReportEmpty = errors.New("nvidia/report: empty input")
|
||||
ErrReportBadJSON = errors.New("nvidia/report: bad JSON envelope")
|
||||
ErrReportBadVersion = errors.New("nvidia/report: unsupported evidence_version")
|
||||
ErrReportMissingNonce = errors.New("nvidia/report: missing nonce")
|
||||
ErrReportBadNonce = errors.New("nvidia/report: nonce must be 32 bytes hex")
|
||||
ErrReportMissingArch = errors.New("nvidia/report: missing architecture")
|
||||
ErrReportUnknownArch = errors.New("nvidia/report: unknown architecture")
|
||||
ErrReportNoMeasurements = errors.New("nvidia/report: no measurements present")
|
||||
)
|
||||
|
||||
@@ -57,16 +57,16 @@ type Measurement struct {
|
||||
// rawReport is the on-the-wire envelope. Field names are the
|
||||
// canonical lower-snake spelling used by the NVIDIA evidence schema.
|
||||
type rawReport struct {
|
||||
EvidenceVersion string `json:"evidence_version"`
|
||||
GPUUUID string `json:"gpu_uuid"`
|
||||
Architecture string `json:"architecture"`
|
||||
DriverVersion string `json:"driver_version"`
|
||||
VBIOSVersion string `json:"vbios_version"`
|
||||
NonceHex string `json:"nonce"`
|
||||
Measurements []Measurement `json:"measurements"`
|
||||
CertChain []string `json:"cert_chain"` // PEM blocks
|
||||
Quote string `json:"attestation_quote"` // base64 binary
|
||||
NVSwitchPresent bool `json:"nvswitch_present"`
|
||||
EvidenceVersion string `json:"evidence_version"`
|
||||
GPUUUID string `json:"gpu_uuid"`
|
||||
Architecture string `json:"architecture"`
|
||||
DriverVersion string `json:"driver_version"`
|
||||
VBIOSVersion string `json:"vbios_version"`
|
||||
NonceHex string `json:"nonce"`
|
||||
Measurements []Measurement `json:"measurements"`
|
||||
CertChain []string `json:"cert_chain"` // PEM blocks
|
||||
Quote string `json:"attestation_quote"` // base64 binary
|
||||
NVSwitchPresent bool `json:"nvswitch_present"`
|
||||
}
|
||||
|
||||
// GPUReport is the parsed, validated GPU evidence report — every field
|
||||
|
||||
@@ -49,11 +49,11 @@ import (
|
||||
|
||||
// Errors returned by the NRAS client.
|
||||
var (
|
||||
ErrNRASNoEndpoint = errors.New("nvidia/nras: endpoint not configured")
|
||||
ErrNRASNoEvidence = errors.New("nvidia/nras: empty evidence")
|
||||
ErrNRASBadStatus = errors.New("nvidia/nras: non-2xx response")
|
||||
ErrNRASBadResponse = errors.New("nvidia/nras: malformed response body")
|
||||
ErrNRASEmptyToken = errors.New("nvidia/nras: token field missing")
|
||||
ErrNRASNoEndpoint = errors.New("nvidia/nras: endpoint not configured")
|
||||
ErrNRASNoEvidence = errors.New("nvidia/nras: empty evidence")
|
||||
ErrNRASBadStatus = errors.New("nvidia/nras: non-2xx response")
|
||||
ErrNRASBadResponse = errors.New("nvidia/nras: malformed response body")
|
||||
ErrNRASEmptyToken = errors.New("nvidia/nras: token field missing")
|
||||
ErrNRASNonceMismatch = errors.New("nvidia/nras: token nonce does not match request")
|
||||
ErrNRASTokenExpired = errors.New("nvidia/nras: token already expired")
|
||||
ErrNRASBadJWT = errors.New("nvidia/nras: token not a valid JWS compact serialization")
|
||||
@@ -71,9 +71,9 @@ var (
|
||||
// VerifyResult. The HTTP client is injectable so tests use httptest
|
||||
// directly.
|
||||
type NRASClient struct {
|
||||
Endpoint string // e.g. "https://nras.attestation.nvidia.com"
|
||||
HTTPClient *http.Client // nil = http.DefaultClient with 15s timeout
|
||||
TrustRoots []TrustRoot // public keys that may sign NRAS tokens
|
||||
Endpoint string // e.g. "https://nras.attestation.nvidia.com"
|
||||
HTTPClient *http.Client // nil = http.DefaultClient with 15s timeout
|
||||
TrustRoots []TrustRoot // public keys that may sign NRAS tokens
|
||||
Now func() time.Time
|
||||
}
|
||||
|
||||
@@ -97,13 +97,13 @@ type VerifyOptions struct {
|
||||
// VerifyResult captures the post-verification facts. Callers fold
|
||||
// these into GPUEvidence.
|
||||
type VerifyResult struct {
|
||||
Token string // raw JWS compact form
|
||||
TokenHash [32]byte // sha256 of the token bytes
|
||||
Nonce [32]byte // echoed from the request — must match GPU report
|
||||
ExpiresAt time.Time // honored if non-zero
|
||||
IssuedAt time.Time // RFC3339 from the response
|
||||
SignerKeyID string // which trust root verified the JWS
|
||||
Architecture string // mirrored back from the report; for audit
|
||||
Token string // raw JWS compact form
|
||||
TokenHash [32]byte // sha256 of the token bytes
|
||||
Nonce [32]byte // echoed from the request — must match GPU report
|
||||
ExpiresAt time.Time // honored if non-zero
|
||||
IssuedAt time.Time // RFC3339 from the response
|
||||
SignerKeyID string // which trust root verified the JWS
|
||||
Architecture string // mirrored back from the report; for audit
|
||||
}
|
||||
|
||||
// Verify uploads evidence and returns the parsed result.
|
||||
@@ -249,14 +249,14 @@ func (c *NRASClient) Verify(ctx context.Context, report []byte, nonce [32]byte,
|
||||
// by CompositeAttestation.
|
||||
func FoldIntoEvidence(r *VerifyResult, report *GPUReport, rim *RIM) GPUEvidence {
|
||||
out := GPUEvidence{
|
||||
UUID: report.UUID,
|
||||
DriverVersion: report.DriverVersion,
|
||||
VBIOSVersion: report.VBIOSVersion,
|
||||
Architecture: report.Architecture,
|
||||
Verified: true,
|
||||
NRASTokenHash: r.TokenHash,
|
||||
Nonce: r.Nonce,
|
||||
VerifiedAt: r.IssuedAt,
|
||||
UUID: report.UUID,
|
||||
DriverVersion: report.DriverVersion,
|
||||
VBIOSVersion: report.VBIOSVersion,
|
||||
Architecture: report.Architecture,
|
||||
Verified: true,
|
||||
NRASTokenHash: r.TokenHash,
|
||||
Nonce: r.Nonce,
|
||||
VerifiedAt: r.IssuedAt,
|
||||
NVSwitchVerified: report.NVSwitchPresent,
|
||||
}
|
||||
if rim != nil {
|
||||
@@ -424,4 +424,3 @@ func truncate(s string, n int) string {
|
||||
}
|
||||
return s[:n] + "..."
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
// CVE-2015-9235 family). The eight cases below pin down the alg ↔
|
||||
// key-type matrix at the dispatch layer:
|
||||
//
|
||||
// 1. EdDSA + ed25519.PublicKey → PASS
|
||||
// 2. ES256 + *ecdsa.PublicKey on P-256 → PASS
|
||||
// 3. PS256 + *rsa.PublicKey → PASS
|
||||
// 4. EdDSA paired with RSA pub → ErrAlgKeyTypeMismatch
|
||||
// 5. ES256 paired with ed25519 pub → ErrAlgKeyTypeMismatch
|
||||
// 6. ES256 paired with ecdsa P-384 → ErrAlgKeyTypeMismatch
|
||||
// 7. alg=none → ErrAlgNoneRefused
|
||||
// 8. alg=Unknown → ErrUnknownAlg
|
||||
// 1. EdDSA + ed25519.PublicKey → PASS
|
||||
// 2. ES256 + *ecdsa.PublicKey on P-256 → PASS
|
||||
// 3. PS256 + *rsa.PublicKey → PASS
|
||||
// 4. EdDSA paired with RSA pub → ErrAlgKeyTypeMismatch
|
||||
// 5. ES256 paired with ed25519 pub → ErrAlgKeyTypeMismatch
|
||||
// 6. ES256 paired with ecdsa P-384 → ErrAlgKeyTypeMismatch
|
||||
// 7. alg=none → ErrAlgNoneRefused
|
||||
// 8. alg=Unknown → ErrUnknownAlg
|
||||
//
|
||||
// Cases 4-6 are the substitution attack: a forged JWS header that
|
||||
// names a different alg than the trust-root's key actually supports.
|
||||
|
||||
@@ -277,4 +277,3 @@ func sortStrings(s []string) {
|
||||
s[j+1] = v
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -113,11 +113,11 @@ func (d *MChainDispatcher) flushLocked(ctx context.Context) error {
|
||||
batchHash := hex.EncodeToString(h.Sum(nil))
|
||||
|
||||
body, err := json.Marshal(map[string]any{
|
||||
"batchHash": batchHash,
|
||||
"fromSeq": from,
|
||||
"toSeq": to,
|
||||
"prevAnchor": d.anchor,
|
||||
"events": d.pend,
|
||||
"batchHash": batchHash,
|
||||
"fromSeq": from,
|
||||
"toSeq": to,
|
||||
"prevAnchor": d.anchor,
|
||||
"events": d.pend,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("audit/mchain: marshal: %w", err)
|
||||
|
||||
@@ -92,7 +92,7 @@ func TestGasStation_AboveFloorSkipped(t *testing.T) {
|
||||
_ = g.Set(GasRule{
|
||||
OrgID: "org1", Chain: "ethereum", ReserveWallet: "reserve",
|
||||
TargetWallets: []string{"hot1"},
|
||||
Floor: big.NewInt(100), Target: big.NewInt(500), CapPerTick: big.NewInt(10000),
|
||||
Floor: big.NewInt(100), Target: big.NewInt(500), CapPerTick: big.NewInt(10000),
|
||||
Enabled: true,
|
||||
})
|
||||
results, err := g.Tick(context.Background())
|
||||
@@ -113,7 +113,7 @@ func TestGasStation_CapPerTickHonored(t *testing.T) {
|
||||
_ = g.Set(GasRule{
|
||||
OrgID: "org1", Chain: "ethereum", ReserveWallet: "reserve",
|
||||
TargetWallets: []string{"hot1"},
|
||||
Floor: big.NewInt(100), Target: big.NewInt(10000), CapPerTick: big.NewInt(50),
|
||||
Floor: big.NewInt(100), Target: big.NewInt(10000), CapPerTick: big.NewInt(50),
|
||||
Enabled: true,
|
||||
})
|
||||
results, _ := g.Tick(context.Background())
|
||||
@@ -136,7 +136,7 @@ func TestGasStation_BalanceError(t *testing.T) {
|
||||
_ = g.Set(GasRule{
|
||||
OrgID: "org1", Chain: "ethereum", ReserveWallet: "reserve",
|
||||
TargetWallets: []string{"hot1"},
|
||||
Floor: big.NewInt(100), Target: big.NewInt(500), CapPerTick: big.NewInt(10000),
|
||||
Floor: big.NewInt(100), Target: big.NewInt(500), CapPerTick: big.NewInt(10000),
|
||||
Enabled: true,
|
||||
})
|
||||
results, _ := g.Tick(context.Background())
|
||||
|
||||
@@ -86,10 +86,10 @@ type BTCBumpInput struct {
|
||||
// BTCBumpPlan is the new fee + the inputs (unchanged) that the caller
|
||||
// must sign into a replacement transaction.
|
||||
type BTCBumpPlan struct {
|
||||
NewFeeSat *big.Int `json:"newFeeSat"`
|
||||
NewFeeRateSatVB int64 `json:"newFeeRateSatVB"`
|
||||
Inputs []string `json:"inputs"`
|
||||
BumpFactorBps int64 `json:"bumpFactorBps"`
|
||||
NewFeeSat *big.Int `json:"newFeeSat"`
|
||||
NewFeeRateSatVB int64 `json:"newFeeRateSatVB"`
|
||||
Inputs []string `json:"inputs"`
|
||||
BumpFactorBps int64 `json:"bumpFactorBps"`
|
||||
}
|
||||
|
||||
// BTCBump computes an RBF-compliant replacement fee.
|
||||
|
||||
@@ -70,13 +70,13 @@ func (r GasRule) Validate() error {
|
||||
|
||||
// TopupReceipt records what a Tick did for one address.
|
||||
type TopupReceipt struct {
|
||||
Address string `json:"address"`
|
||||
BeforeWei string `json:"beforeWei"`
|
||||
AfterWei string `json:"afterWei,omitempty"`
|
||||
SentWei string `json:"sentWei"`
|
||||
TxHash string `json:"txHash"`
|
||||
SkipReason string `json:"skipReason,omitempty"`
|
||||
OccurredAt time.Time `json:"occurredAt"`
|
||||
Address string `json:"address"`
|
||||
BeforeWei string `json:"beforeWei"`
|
||||
AfterWei string `json:"afterWei,omitempty"`
|
||||
SentWei string `json:"sentWei"`
|
||||
TxHash string `json:"txHash"`
|
||||
SkipReason string `json:"skipReason,omitempty"`
|
||||
OccurredAt time.Time `json:"occurredAt"`
|
||||
}
|
||||
|
||||
// TickResult is the per-rule outcome of one GasStation tick.
|
||||
|
||||
@@ -30,17 +30,17 @@ import (
|
||||
// for small amounts; for large transfers the absolute fee dominates and
|
||||
// providers should report it precisely.
|
||||
type Quote struct {
|
||||
Provider string `json:"provider"`
|
||||
Route string `json:"route"` // human-readable route description
|
||||
Asset string `json:"asset"`
|
||||
Amount string `json:"amount"` // base-10 in smallest unit
|
||||
Dest string `json:"dest"`
|
||||
Chain string `json:"chain"` // destination chain (CAIP-2)
|
||||
FeeBps int64 `json:"feeBps"` // total cost in basis points
|
||||
FeeAbs string `json:"feeAbs"` // absolute fee in smallest unit (informational)
|
||||
ETA time.Duration `json:"eta"` // estimated time to settle
|
||||
ValidUntil time.Time `json:"validUntil"`
|
||||
Provider_ Provider `json:"-"` // back-reference for execution
|
||||
Provider string `json:"provider"`
|
||||
Route string `json:"route"` // human-readable route description
|
||||
Asset string `json:"asset"`
|
||||
Amount string `json:"amount"` // base-10 in smallest unit
|
||||
Dest string `json:"dest"`
|
||||
Chain string `json:"chain"` // destination chain (CAIP-2)
|
||||
FeeBps int64 `json:"feeBps"` // total cost in basis points
|
||||
FeeAbs string `json:"feeAbs"` // absolute fee in smallest unit (informational)
|
||||
ETA time.Duration `json:"eta"` // estimated time to settle
|
||||
ValidUntil time.Time `json:"validUntil"`
|
||||
Provider_ Provider `json:"-"` // back-reference for execution
|
||||
}
|
||||
|
||||
// TransferRequest is what SmartTransfer.Quote consumes.
|
||||
@@ -176,9 +176,9 @@ func (s *SmartTransfer) Best(ctx context.Context, req TransferRequest) (Quote, e
|
||||
// callers feed the resulting Quote into their existing sign + broadcast
|
||||
// path.
|
||||
type OnChainProvider struct {
|
||||
chain string
|
||||
bps int64
|
||||
eta time.Duration
|
||||
chain string
|
||||
bps int64
|
||||
eta time.Duration
|
||||
}
|
||||
|
||||
// NewOnChainProvider constructs a provider for one chain. bps is the
|
||||
@@ -217,11 +217,11 @@ func (p *OnChainProvider) QuoteTransfer(_ context.Context, req TransferRequest)
|
||||
// BridgeProvider quotes a cross-chain transfer. RouteName is used in the
|
||||
// quote's Route field for operator visibility.
|
||||
type BridgeProvider struct {
|
||||
name string
|
||||
bps int64
|
||||
srcChain string
|
||||
dstChain string
|
||||
eta time.Duration
|
||||
name string
|
||||
bps int64
|
||||
srcChain string
|
||||
dstChain string
|
||||
eta time.Duration
|
||||
}
|
||||
|
||||
// NewBridgeProvider constructs a bridge provider. The (src,dst) pair is
|
||||
|
||||
@@ -30,7 +30,7 @@ type BiometricProof struct {
|
||||
type DeviceRegistration struct {
|
||||
DeviceID string `json:"device_id"`
|
||||
DeviceType string `json:"device_type"` // ios, android
|
||||
PublicKey []byte `json:"public_key"` // Secure Enclave P-256 uncompressed public key
|
||||
PublicKey []byte `json:"public_key"` // Secure Enclave P-256 uncompressed public key
|
||||
BiometricType string `json:"biometric_type"` // face_id, touch_id, fingerprint
|
||||
WalletID string `json:"wallet_id"`
|
||||
ParticipantID string `json:"participant_id"` // MPC participant slot (e.g., "user")
|
||||
@@ -45,9 +45,9 @@ const maxProofAge = 30 * time.Second
|
||||
// nonceGuard tracks used nonces to prevent replay attacks.
|
||||
// Nonces expire after 2x the proof age to bound memory usage.
|
||||
type nonceGuard struct {
|
||||
mu sync.Mutex
|
||||
used map[string]time.Time
|
||||
ttl time.Duration
|
||||
mu sync.Mutex
|
||||
used map[string]time.Time
|
||||
ttl time.Duration
|
||||
}
|
||||
|
||||
func newNonceGuard() *nonceGuard {
|
||||
|
||||
@@ -26,7 +26,7 @@ type TradeApprovalNotification struct {
|
||||
DeviceType string `json:"device_type"` // ios, android
|
||||
TradeID string `json:"trade_id"`
|
||||
Symbol string `json:"symbol"`
|
||||
Side string `json:"side"` // buy, sell
|
||||
Side string `json:"side"` // buy, sell
|
||||
Quantity string `json:"quantity"`
|
||||
Price string `json:"price"`
|
||||
TotalValue string `json:"total_value"`
|
||||
@@ -98,7 +98,7 @@ func (f *FCMNotifier) SendTradeApproval(ctx context.Context, req TradeApprovalNo
|
||||
},
|
||||
"apns": map[string]interface{}{
|
||||
"headers": map[string]string{
|
||||
"apns-priority": "10",
|
||||
"apns-priority": "10",
|
||||
"apns-push-type": "alert",
|
||||
},
|
||||
},
|
||||
|
||||
+33
-33
@@ -406,13 +406,13 @@ func init() { orm.Register[Settlement]("settlement") }
|
||||
// (e.g., iCloud Keychain + Platform HSM) so that no single party can decrypt alone.
|
||||
type WalletBackup struct {
|
||||
orm.Model[WalletBackup]
|
||||
OrgID string `json:"orgId"`
|
||||
WalletID string `json:"walletId"`
|
||||
BackupID string `json:"backupId"` // unique backup identifier
|
||||
Threshold int `json:"threshold"` // T shards required to reconstruct
|
||||
TotalShards int `json:"totalShards"`
|
||||
EncryptedKeyShare []byte `json:"encryptedKeyShare,omitempty"` // AES-256-GCM(ECDH(user_passkey, ephemeral)) encrypted key share
|
||||
EphemeralPublicKey string `json:"ephemeralPublicKey,omitempty"` // P-256 ephemeral pubkey for user-side ECDH decryption
|
||||
OrgID string `json:"orgId"`
|
||||
WalletID string `json:"walletId"`
|
||||
BackupID string `json:"backupId"` // unique backup identifier
|
||||
Threshold int `json:"threshold"` // T shards required to reconstruct
|
||||
TotalShards int `json:"totalShards"`
|
||||
EncryptedKeyShare []byte `json:"encryptedKeyShare,omitempty"` // AES-256-GCM(ECDH(user_passkey, ephemeral)) encrypted key share
|
||||
EphemeralPublicKey string `json:"ephemeralPublicKey,omitempty"` // P-256 ephemeral pubkey for user-side ECDH decryption
|
||||
Shards []BackupShard `json:"shards,omitempty"`
|
||||
Status string `json:"status"` // active, recovered, revoked
|
||||
UserPasskeyFingerprint string `json:"userPasskeyFingerprint,omitempty"` // P-256 pubkey that encrypts the backup shard — company CANNOT decrypt
|
||||
@@ -440,8 +440,8 @@ type DeviceEnrollment struct {
|
||||
DeviceID string `json:"deviceId"`
|
||||
DeviceType string `json:"deviceType"` // ios, android
|
||||
BiometricType string `json:"biometricType"` // face_id, touch_id, fingerprint
|
||||
PublicKey string `json:"publicKey"` // Base64-encoded P-256 Secure Enclave public key
|
||||
ParticipantID string `json:"participantId"` // MPC participant slot (e.g., "user")
|
||||
PublicKey string `json:"publicKey"` // Base64-encoded P-256 Secure Enclave public key
|
||||
ParticipantID string `json:"participantId"` // MPC participant slot (e.g., "user")
|
||||
Challenge string `json:"challenge,omitempty"`
|
||||
Attestation string `json:"attestation,omitempty"` // Device attestation object
|
||||
PushToken string `json:"pushToken,omitempty"` // FCM or APNS push notification token
|
||||
@@ -462,25 +462,25 @@ func init() { orm.Register[DeviceEnrollment]("device-enrollment") }
|
||||
// pending_approval → expired
|
||||
type PendingTrade struct {
|
||||
orm.Model[PendingTrade]
|
||||
OrgID string `json:"orgId"`
|
||||
UserID string `json:"userId"`
|
||||
WalletID string `json:"walletId"`
|
||||
DeviceID string `json:"deviceId,omitempty"`
|
||||
Symbol string `json:"symbol"`
|
||||
Side string `json:"side"` // buy, sell
|
||||
Quantity string `json:"quantity"`
|
||||
Price string `json:"price"`
|
||||
TotalValue string `json:"totalValue"`
|
||||
TradeID string `json:"tradeId,omitempty"` // ATS trade record ID
|
||||
OrderID string `json:"orderId,omitempty"` // ATS order record ID
|
||||
MessageHash string `json:"messageHash"` // Settlement tx data to sign (hex)
|
||||
IntentID *string `json:"intentId,omitempty"` // MPC intent created after approval
|
||||
TxHash *string `json:"txHash,omitempty"` // On-chain tx hash after broadcast
|
||||
Status string `json:"status"`
|
||||
Reason *string `json:"reason,omitempty"` // Rejection reason
|
||||
ExpiresAt time.Time `json:"expiresAt"` // 5 minute approval window
|
||||
ApprovedAt *time.Time `json:"approvedAt,omitempty"`
|
||||
SignedAt *time.Time `json:"signedAt,omitempty"`
|
||||
OrgID string `json:"orgId"`
|
||||
UserID string `json:"userId"`
|
||||
WalletID string `json:"walletId"`
|
||||
DeviceID string `json:"deviceId,omitempty"`
|
||||
Symbol string `json:"symbol"`
|
||||
Side string `json:"side"` // buy, sell
|
||||
Quantity string `json:"quantity"`
|
||||
Price string `json:"price"`
|
||||
TotalValue string `json:"totalValue"`
|
||||
TradeID string `json:"tradeId,omitempty"` // ATS trade record ID
|
||||
OrderID string `json:"orderId,omitempty"` // ATS order record ID
|
||||
MessageHash string `json:"messageHash"` // Settlement tx data to sign (hex)
|
||||
IntentID *string `json:"intentId,omitempty"` // MPC intent created after approval
|
||||
TxHash *string `json:"txHash,omitempty"` // On-chain tx hash after broadcast
|
||||
Status string `json:"status"`
|
||||
Reason *string `json:"reason,omitempty"` // Rejection reason
|
||||
ExpiresAt time.Time `json:"expiresAt"` // 5 minute approval window
|
||||
ApprovedAt *time.Time `json:"approvedAt,omitempty"`
|
||||
SignedAt *time.Time `json:"signedAt,omitempty"`
|
||||
StatusHistory []StatusTransition `json:"statusHistory,omitempty"`
|
||||
}
|
||||
|
||||
@@ -557,9 +557,9 @@ const (
|
||||
// `KeyRef` is either an IAM user/service-principal ID (for human signers)
|
||||
// or a KMS key path (for HSM signers, always `providers/{OrgID}/...`).
|
||||
type TreasurySigner struct {
|
||||
Role string `json:"role"` // compliance_officer | treasurer | platform_hsm | backup_hsm | regulator
|
||||
KeyRef string `json:"keyRef"` // user ID for humans, KMS key path for HSMs
|
||||
DisplayName string `json:"displayName,omitempty"`
|
||||
Role string `json:"role"` // compliance_officer | treasurer | platform_hsm | backup_hsm | regulator
|
||||
KeyRef string `json:"keyRef"` // user ID for humans, KMS key path for HSMs
|
||||
DisplayName string `json:"displayName,omitempty"`
|
||||
RotatedAt *time.Time `json:"rotatedAt,omitempty"`
|
||||
}
|
||||
|
||||
@@ -568,8 +568,8 @@ type TreasurySigner struct {
|
||||
// value "inf" matches arbitrarily large operations. `Threshold` is the
|
||||
// number of signer approvals required when the op's value falls in this tier.
|
||||
type TreasuryTier struct {
|
||||
MaxValue string `json:"maxValue"` // "100000", "1000000", "inf"
|
||||
Threshold int `json:"threshold"` // e.g. 3, 4, 5
|
||||
MaxValue string `json:"maxValue"` // "100000", "1000000", "inf"
|
||||
Threshold int `json:"threshold"` // e.g. 3, 4, 5
|
||||
RequireRegulator bool `json:"requireRegulator,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -199,8 +199,8 @@ func (g *GCPProvider) kmsSign(ctx context.Context, message []byte) ([]byte, erro
|
||||
crc := crc32.Checksum(message, crc32.MakeTable(crc32.Castagnoli))
|
||||
|
||||
result, err := g.kmsClient.AsymmetricSign(ctx, &kmspb.AsymmetricSignRequest{
|
||||
Name: g.keyVersionName(),
|
||||
Data: message,
|
||||
Name: g.keyVersionName(),
|
||||
Data: message,
|
||||
DataCrc32C: &kmspb.Int64Value{Value: int64(crc)},
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
+5
-5
@@ -56,9 +56,9 @@ type Config struct {
|
||||
|
||||
// FileConfig configures the local filesystem provider.
|
||||
type FileConfig struct {
|
||||
BasePath string `yaml:"base_path" mapstructure:"base_path"` // directory containing key files
|
||||
Encrypted bool `yaml:"encrypted" mapstructure:"encrypted"` // whether keys are age-encrypted
|
||||
HexEncoded bool `yaml:"hex_encoded" mapstructure:"hex_encoded"` // whether key files contain hex (default true)
|
||||
BasePath string `yaml:"base_path" mapstructure:"base_path"` // directory containing key files
|
||||
Encrypted bool `yaml:"encrypted" mapstructure:"encrypted"` // whether keys are age-encrypted
|
||||
HexEncoded bool `yaml:"hex_encoded" mapstructure:"hex_encoded"` // whether key files contain hex (default true)
|
||||
}
|
||||
|
||||
// AWSConfig configures the AWS KMS / CloudHSM provider.
|
||||
@@ -73,7 +73,7 @@ type AWSConfig struct {
|
||||
// GCPConfig configures the Google Cloud KMS provider.
|
||||
type GCPConfig struct {
|
||||
Project string `yaml:"project" mapstructure:"project"`
|
||||
Location string `yaml:"location" mapstructure:"location"` // e.g. "global", "us-east1"
|
||||
Location string `yaml:"location" mapstructure:"location"` // e.g. "global", "us-east1"
|
||||
KeyRing string `yaml:"key_ring" mapstructure:"key_ring"`
|
||||
KeyName string `yaml:"key_name" mapstructure:"key_name"`
|
||||
HSMLevel string `yaml:"hsm_level" mapstructure:"hsm_level"` // "SOFTWARE" or "HSM" (default HSM)
|
||||
@@ -81,7 +81,7 @@ type GCPConfig struct {
|
||||
|
||||
// AzureConfig configures the Azure Key Vault / Managed HSM provider.
|
||||
type AzureConfig struct {
|
||||
VaultURL string `yaml:"vault_url" mapstructure:"vault_url"` // e.g. "https://mpc-hsm.managedhsm.azure.net"
|
||||
VaultURL string `yaml:"vault_url" mapstructure:"vault_url"` // e.g. "https://mpc-hsm.managedhsm.azure.net"
|
||||
KeyName string `yaml:"key_name" mapstructure:"key_name"`
|
||||
TenantID string `yaml:"tenant_id" mapstructure:"tenant_id"`
|
||||
ClientID string `yaml:"client_id" mapstructure:"client_id"`
|
||||
|
||||
@@ -187,7 +187,7 @@ func announce(ctx context.Context, cfg Config, id *Identity) error {
|
||||
return nil
|
||||
}
|
||||
body, err := json.Marshal(map[string]any{
|
||||
"node_id": id.NodeID,
|
||||
"node_id": id.NodeID,
|
||||
"public_key": hex.EncodeToString(id.PublicKey),
|
||||
"ts": time.Now().UTC().Format(time.RFC3339Nano),
|
||||
})
|
||||
|
||||
+17
-17
@@ -54,11 +54,11 @@ type ConsensusKV struct {
|
||||
pendingCh chan struct{}
|
||||
|
||||
// Node identity (dual keys)
|
||||
nodeID string
|
||||
edPrivKey ed25519.PrivateKey // Classical signing
|
||||
edPubKey ed25519.PublicKey
|
||||
pqPrivKey *mldsa.PrivateKey // Post-quantum signing (ML-DSA-65)
|
||||
pqPubKey *mldsa.PublicKey
|
||||
nodeID string
|
||||
edPrivKey ed25519.PrivateKey // Classical signing
|
||||
edPubKey ed25519.PublicKey
|
||||
pqPrivKey *mldsa.PrivateKey // Post-quantum signing (ML-DSA-65)
|
||||
pqPubKey *mldsa.PublicKey
|
||||
|
||||
// Optional KMS/HSM signer (overrides local keys)
|
||||
signer Signer
|
||||
@@ -66,7 +66,7 @@ type ConsensusKV struct {
|
||||
|
||||
// Validator set
|
||||
validators map[string]*ValidatorKeys // nodeID -> dual keys
|
||||
threshold int // votes for finality
|
||||
threshold int // votes for finality
|
||||
|
||||
// Chain state
|
||||
blockHeight uint64
|
||||
@@ -112,8 +112,8 @@ type ConsensusBlock struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Mutations []KVMutation `json:"mutations"`
|
||||
ProposerID string `json:"proposer_id"`
|
||||
EdSig []byte `json:"ed_sig"` // Ed25519 proposer signature
|
||||
PQSig []byte `json:"pq_sig"` // ML-DSA-65 proposer signature
|
||||
EdSig []byte `json:"ed_sig"` // Ed25519 proposer signature
|
||||
PQSig []byte `json:"pq_sig"` // ML-DSA-65 proposer signature
|
||||
}
|
||||
|
||||
// BlockHash computes the deterministic hash of a block (excludes signatures).
|
||||
@@ -166,12 +166,12 @@ type Signer interface {
|
||||
// ConsensusKVConfig configures the private blockchain consensus.
|
||||
type ConsensusKVConfig struct {
|
||||
NodeID string
|
||||
EdPrivateKey ed25519.PrivateKey // Classical identity key (nil if using Signer)
|
||||
PQPrivateKey *mldsa.PrivateKey // Post-quantum identity key (nil = auto-generate)
|
||||
Signer Signer // Optional KMS/HSM signer (overrides local keys)
|
||||
SignerKeyID string // Key ID for Signer
|
||||
Validators map[string]*ValidatorKeys // All cluster nodes
|
||||
Threshold int // Votes for finality (default: floor(n/2)+1)
|
||||
EdPrivateKey ed25519.PrivateKey // Classical identity key (nil if using Signer)
|
||||
PQPrivateKey *mldsa.PrivateKey // Post-quantum identity key (nil = auto-generate)
|
||||
Signer Signer // Optional KMS/HSM signer (overrides local keys)
|
||||
SignerKeyID string // Key ID for Signer
|
||||
Validators map[string]*ValidatorKeys // All cluster nodes
|
||||
Threshold int // Votes for finality (default: floor(n/2)+1)
|
||||
NATSConn *nats.Conn
|
||||
ChainID string // Default "mpc"
|
||||
ProposalInterval time.Duration // Default 50ms
|
||||
@@ -743,10 +743,10 @@ func (c *ConsensusKV) LoadSnapshot(snap map[string][]byte, height uint64) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ConsensusKV) Height() uint64 { return c.blockHeight }
|
||||
func (c *ConsensusKV) Height() uint64 { return c.blockHeight }
|
||||
func (c *ConsensusKV) LastBlockHash() [32]byte { return c.lastBlockHash }
|
||||
func (c *ConsensusKV) ValidatorCount() int { return len(c.validators) }
|
||||
func (c *ConsensusKV) Threshold() int { return c.threshold }
|
||||
func (c *ConsensusKV) ValidatorCount() int { return len(c.validators) }
|
||||
func (c *ConsensusKV) Threshold() int { return c.threshold }
|
||||
|
||||
// PQPublicKey returns this node's ML-DSA-65 public key bytes (for registration).
|
||||
func (c *ConsensusKV) PQPublicKey() []byte {
|
||||
|
||||
@@ -310,7 +310,7 @@ func TestConsensusKV_PQKeyGeneration(t *testing.T) {
|
||||
priv, err := mldsa.GenerateKey(rand.Reader, mldsa.MLDSA65)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, mldsa.MLDSA65PublicKeySize, len(priv.PublicKey.Bytes())) // 1,952 bytes
|
||||
assert.Equal(t, mldsa.MLDSA65PublicKeySize, len(priv.PublicKey.Bytes())) // 1,952 bytes
|
||||
assert.Equal(t, mldsa.MLDSA65PrivateKeySize, len(priv.Bytes())) // 4,032 bytes
|
||||
|
||||
// Sign and verify
|
||||
|
||||
@@ -445,4 +445,3 @@ func (s *SQLiteMeta) Audit(entry AuditEntry) error {
|
||||
func (s *SQLiteMeta) Close() error {
|
||||
return s.db.Close()
|
||||
}
|
||||
|
||||
|
||||
@@ -57,19 +57,19 @@ type CanonicalIntent struct {
|
||||
SessionID string `cbor:"session_id"`
|
||||
WalletID string `cbor:"wallet_id"`
|
||||
WalletTier WalletTier `cbor:"wallet_tier"`
|
||||
Chain string `cbor:"chain"` // CAIP-2 e.g. "eip155:1"
|
||||
Chain string `cbor:"chain"` // CAIP-2 e.g. "eip155:1"
|
||||
Asset string `cbor:"asset"`
|
||||
From string `cbor:"from"`
|
||||
To string `cbor:"to"`
|
||||
Amount string `cbor:"amount"` // arbitrary-precision decimal as string
|
||||
Amount string `cbor:"amount"` // arbitrary-precision decimal as string
|
||||
MaxFee string `cbor:"max_fee"`
|
||||
Nonce string `cbor:"nonce"`
|
||||
CalldataHash [32]byte `cbor:"calldata_hash"` // keccak256 / sha256 of contract calldata
|
||||
CalldataHash [32]byte `cbor:"calldata_hash"` // keccak256 / sha256 of contract calldata
|
||||
HumanSummary string `cbor:"human_summary"`
|
||||
PolicyID string `cbor:"policy_id"`
|
||||
PolicyHash [32]byte `cbor:"policy_hash"` // hash of the active policy bundle this intent was approved against
|
||||
PolicyHash [32]byte `cbor:"policy_hash"` // hash of the active policy bundle this intent was approved against
|
||||
RiskVerdictID string `cbor:"risk_verdict_id"`
|
||||
SimulationHash [32]byte `cbor:"simulation_hash"` // hash of the EVM simulation result
|
||||
SimulationHash [32]byte `cbor:"simulation_hash"` // hash of the EVM simulation result
|
||||
ExpiresAt time.Time `cbor:"expires_at"`
|
||||
Approvals []ApprovalSignature `cbor:"approvals"` // executive sigs over the digest; excluded from digest
|
||||
NodeAttestations []NodeAttestation `cbor:"node_attestations"` // per-node verifier decisions; excluded from digest
|
||||
|
||||
Reference in New Issue
Block a user