Nutzerhandbuch für Multi-Party Compute

In dieser Anleitung wird beschrieben, wie Sie mit der Multi-Party Compute API verteilte Schlüssel, Teilnehmer und Richtlinien verwalten. Sie richtet sich an Kunden, die die Vorabversion nutzen und die wichtigsten User Journeys kennenlernen möchten.

Übersicht

Die MPC API besteht aus zwei Hauptkomponenten:

  1. Core MPC verwaltet den Lebenszyklus von verteilten Schlüsseln, Vorschlägen und Signaturzeremonien.
  2. Die Policy Engine setzt die Governance durch und autorisiert Vorgänge, bevor sie ausgeführt werden.

Eine detaillierte Aufschlüsselung der Interaktion zwischen API-Zugriff und kryptografischer Signatur finden Sie unter Sicherheit und Autorisierung.

Vorbereitung

Prüfen Sie zuerst, ob Sie Folgendes haben:

  1. Ein Google Cloud -Projekt. Informationen zum Erstellen eines Projekts finden Sie unter Projekte erstellen.
  2. Wenn Sie Cloud Shell nicht verwenden, muss die gcloud-Befehlszeile installiert und authentifiziert sein.
  3. Erforderliche APIs aktiviert, falls noch nicht geschehen. Sie benötigen zwei APIs: die neue MPC API, die in diesem Leitfaden verwendet wird, und den Cloud Key Management Service, wenn Sie ihn für die Generierung/Signierung von Assent-Schlüsseln verwenden.

      gcloud services enable mpc.googleapis.com cloudkms.googleapis.com
    

    So rufen Sie die aktivierten APIs auf:

      gcloud services list --enabled
    
  4. Python 3.9 oder höher und die Hilfsbibliotheken für die Anleitung sind installiert:

      pip install embit eth-account eth-utils requests
    
  5. (Optional) Wenn sich Ihre Organisation mit einem externen Identitätsanbieter (z. B. Okta oder Microsoft Entra ID) ohne Google-Konten authentifiziert, finden Sie unter Workforce Identity-Föderation (BYOID) konfigurieren Informationen zum Konfigurieren von Standardanmeldedaten für Anwendungen (Application Default Credentials, ADC) mit Ihrer föderierten Identität.

Einrichtung

Aktualisieren Sie die folgenden Variablen, um die Befehle in diesem Leitfaden zu füllen.

Erforderliche Einrichtungsschritte

Sie müssen diese Parameter anpassen. Die Anleitung funktioniert nicht mit den Platzhalterwerten.

Parameter Wert Beschreibung
Projekt-ID my-project Die Projekt-ID des Nutzers, die Sie uns für das Onboarding in die Vorabversion zur Verfügung gestellt haben.
Domain-ID 67317436-0f29-ba9e-9146-d11c0bd8df9b Die eindeutige Domain-ID, die wir Ihrer Instanz zugewiesen haben.
Instanzpfad-ID mpc-unit-bob-123 Die Pfadinstanz-ID, die wir Ihrer Instanz zugewiesen haben.
Standort us-central1 Bereitstellungsstandort. Muss us-central1 sein (andere Regionen werden in der Vorabversion nicht unterstützt).

Tutorial-Ressourcenlabels

Dies sind Namen für Ressourcen, die in dieser Anleitung erstellt werden. Die Standardwerte sind gültig und können beibehalten werden, um den Einrichtungsaufwand zu minimieren.

Parameter Wert Beschreibung
Cloud KMS-Schlüsselbund-ID mpc-assent-keys Cloud Key Management Service-Schlüsselbund für externe Assent-Schlüssel, die in Cloud KMS gespeichert sind.
ID des 1. Unterzeichners signer-alice MPC-Unterzeichner 1.
Signer 2 ID signer-bob MPC-Signierer 2.
Signer 3 ID signer-charlie MPC-Signierer 3.
Policy Author 1 ID author-dave Richtlinienautor 1.
Policy Author 2 ID author-eve Richtlinienautor 2.
Richtlinien-ID ethereum-transfer-policy Richtlinien-ID (z. B. ethereum-transfer-policy oder bitcoin-psbt-policy).
Schlüsselbund-ID my-key-ring KeyRing-ID.
Schlüssel-ID my-key Schlüssel-ID.
Angebots-ID my-proposal Angebots-ID.

Schritt 1: Assent-Schlüssel generieren

Wenn Sie die MPC API verwenden möchten, müssen Sie Schlüsselpaare für die folgenden Rollen generieren. Beachten Sie, dass es sich hierbei um externe kryptografische Zustimmungsschlüssel handelt, die sich von den verteilten MPC-Schwellenwert-Schlüsselfragmenten unterscheiden, die intern während der Einrichtung generiert werden.

  • Richtlinienautoren: Autorisieren Governance-Richtlinien.
  • Signers: Stimmen über Vorschläge ab und genehmigen sie.

A. Teilnehmerschlüssel (secp256k1)

Generieren Sie die erforderlichen kryptografischen Schlüssel für Ihre Teilnehmer. Diese Befehle sind idempotent.

Cloud KMS

Wir empfehlen, Assent-Schlüssel im Cloud Key Management Service zu verwalten. Ihre Identität muss Berechtigungen zum Erstellen von Schlüsselbunden, Erstellen von Schlüsseln, Signieren und Aufrufen von Prüfern für öffentliche Schlüssel (z. B. roles/cloudkms.admin und roles/cloudkms.signerVerifier) haben. Sie müssen einen KeyRing erstellt haben und dann einen secp256k1-Schlüssel für alle Ihre Signierer generieren.

provision_keyring() {
  if gcloud kms keyrings describe "mpc-assent-keys" --location="us-central1" > /dev/null 2>&1; then
    echo "KeyRing 'mpc-assent-keys' already exists."
  else
    echo "KeyRing 'mpc-assent-keys' not found. Creating it..."
    gcloud kms keyrings create "mpc-assent-keys" --location="us-central1"
  fi
}

provision_kms_key() {
  local id=$1
  if gcloud kms keys describe "${id}-key" --keyring="mpc-assent-keys" --location="us-central1" > /dev/null 2>&1; then
    echo "KMS key '${id}-key' already exists."
  else
    echo "KMS key '${id}-key' not found. Creating it..."
    gcloud kms keys create "${id}-key" \
      --keyring="mpc-assent-keys" \
      --location="us-central1" \
      --purpose=asymmetric-signing \
      --default-algorithm=ec-sign-secp256k1-sha256 \
      --protection-level=hsm
  fi

  gcloud kms keys versions get-public-key 1 \
    --key="${id}-key" \
    --keyring="mpc-assent-keys" \
    --location="us-central1" \
    --output-file="${id}_pub.pem"

  echo "Retrieved public key from KMS and saved to ${id}_pub.pem:"
  cat "${id}_pub.pem"
}

# Run Setup
provision_keyring

# Generate Keys
for ID in signer-alice signer-bob signer-charlie; do
  echo "Generating KMS key for $ID..."
  provision_kms_key "$ID"
done

OpenSSL

Sie können Standard-OpenSSL verwenden, um secp256k1-Schlüssel für jeden Teilnehmer zu generieren.

provision_openssl_key() {
  local id=$1
  if [[ ! -f "${id}_priv.pem" ]]; then
    openssl ecparam -name secp256k1 -genkey -noout -out "${id}_priv.pem"
    openssl ec -in "${id}_priv.pem" -pubout -out "${id}_pub.pem"
    echo "Wrote private key to ${id}_priv.pem and public key to ${id}_pub.pem:"
    cat "${id}_pub.pem"
  else
    echo "Private key file '${id}_priv.pem' already exists. Skipping OpenSSL generation."
    echo "Using existing public key from '${id}_pub.pem':"
    cat "${id}_pub.pem"
  fi
}

for ID in signer-alice signer-bob signer-charlie; do
  echo "Generating OpenSSL key for $ID..."
  provision_openssl_key "$ID"
done

B. Richtlinienautor-Schlüssel (secp256k1)

Cloud KMS

Wir empfehlen, diese Zustimmungsschlüssel im Cloud Key Management Service zu verwalten. Ihre Identität muss Berechtigungen zum Erstellen von Schlüsselbunden, Erstellen von Schlüsseln, Signieren und Aufrufen von Prüfern für öffentliche Schlüssel (z. B. roles/cloudkms.admin und roles/cloudkms.signerVerifier) haben. Sie müssen einen KeyRing erstellt haben und dann einen secp256k1-Schlüssel für alle Richtlinienautoren generieren.

provision_keyring() {
  if gcloud kms keyrings describe "mpc-assent-keys" --location="us-central1" > /dev/null 2>&1; then
    echo "KeyRing 'mpc-assent-keys' already exists."
  else
    echo "KeyRing 'mpc-assent-keys' not found. Creating it..."
    gcloud kms keyrings create "mpc-assent-keys" --location="us-central1"
  fi
}

provision_kms_key() {
  local id=$1
  if gcloud kms keys describe "${id}-key" --keyring="mpc-assent-keys" --location="us-central1" > /dev/null 2>&1; then
    echo "KMS key '${id}-key' already exists."
  else
    echo "KMS key '${id}-key' not found. Creating it..."
    gcloud kms keys create "${id}-key" \
      --keyring="mpc-assent-keys" \
      --location="us-central1" \
      --purpose=asymmetric-signing \
      --default-algorithm=ec-sign-secp256k1-sha256 \
      --protection-level=hsm
  fi

  gcloud kms keys versions get-public-key 1 \
    --key="${id}-key" \
    --keyring="mpc-assent-keys" \
    --location="us-central1" \
    --output-file="${id}_pub.pem"

  echo "Retrieved public key from KMS and saved to ${id}_pub.pem:"
  cat "${id}_pub.pem"
}

provision_keyring

for ID in author-dave author-eve; do
  echo "Generating KMS key for $ID..."
  provision_kms_key "$ID"
done

OpenSSL

Richtlinienautoren verwenden die secp256k1-Kurve zum Signieren von Governance-Richtlinien und administrativen Aktionen. Sie können diese Schlüssel mit Standard-OpenSSL generieren.

provision_openssl_key() {
  local id=$1
  if [[ ! -f "${id}_priv.pem" ]]; then
    openssl ecparam -name secp256k1 -genkey -noout -out "${id}_priv.pem"
    openssl ec -in "${id}_priv.pem" -pubout -out "${id}_pub.pem"
    echo "Wrote private key to ${id}_priv.pem and public key to ${id}_pub.pem:"
    cat "${id}_pub.pem"
  else
    echo "Private key file '${id}_priv.pem' already exists. Skipping OpenSSL generation."
    echo "Using existing public key from '${id}_pub.pem':"
    cat "${id}_pub.pem"
  fi
}

for ID in author-dave author-eve; do
  echo "Generating OpenSSL key for $ID..."
  provision_openssl_key "$ID"
done

Schritt 2: Teilnehmer registrieren

Bevor eine Entität am MPC-Ablauf (als Signer) oder am Richtlinienablauf (als Policy Author) teilnehmen kann, muss sie als Participant registriert werden.

Die Registrierung erfordert eine Out-of-Band-Koordination zwischen dem Administrator und dem Teilnehmer:

  1. Die Entität (Signierer oder Autor) generiert ihr Schlüsselpaar lokal und überträgt ihren öffentlichen Schlüssel sicher an den Administrator.
  2. Der Administrator (mit standardmäßigen Google Cloud -IAM-Berechtigungen) ruft die API auf, um den öffentlichen Schlüssel zu registrieren.

curl

provision_participant() {
  local id=$1
  local name=$2
  local pubkey_b64

  # Read the public key from the file generated in Step 1
  echo "Reading public key from ${id}_pub.pem..."
  # Public key must be represented as Base64 encoded bytes of the PEM/DER format.
  pubkey_b64=$(openssl base64 -A -in "${id}_pub.pem")

  # Check existence
  if curl -s -f -o /dev/null -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
    -H "X-Goog-User-Project: my-project" \
    "https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/mpc/signers/$id"; then
    echo "Participant '$id' already exists. Skipping registration."
  else
    echo "Participant '$id' not found. Registering..."
    curl -X POST \
      -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
      -H "X-Goog-User-Project: my-project" \
      -H "Content-Type: application/json" \
      -d "{
        \"displayName\": \"$name\",
        \"activePublicAssentKey\": {
          \"publicKey\": \"$pubkey_b64\",
          \"algorithm\": \"PUBLIC_KEY_SIGNATURE_ALGORITHM_ECDSA_SECP256K1\"
        }
      }" \
      "https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/mpc/signers?participant_id=$id" | jq
  fi
}

echo "Registering Signers..."
for ID in signer-alice signer-bob signer-charlie; do
  provision_participant "$ID" "Signer $ID"
done

echo "Registering Policy Authors..."
for ID in author-dave author-eve; do
  provision_participant "$ID" "Author $ID"
done

Schritt 3: Policy Engine konfigurieren

Konfigurieren Sie die Policy Engine, indem Sie das Trust Bundle einrichten und Richtlinien erstellen.

A. Policy Author Trust Bundle initialisieren

Legen Sie fest, wer die autorisierten Richtlinienautoren sind. Dies ist eine Singleton-Ressource für die Instanz.

curl

# Get current sequence from the API. If null (initial state), fallback to 0.
NEXT_BUNDLE_SEQUENCE=$(curl -s -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: my-project" \
  "https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/policyEngine/policyAuthorTrustBundle" \
  | jq -r '.sequenceNumber // 0')

# Calculate higher sequence number.
NEXT_BUNDLE_SEQUENCE=$((NEXT_BUNDLE_SEQUENCE + 1))

curl -X PATCH \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: my-project" \
  -H "Content-Type: application/json" \
  -d '{
    "sequenceNumber": '"$NEXT_BUNDLE_SEQUENCE"',
    "authors": [
      {"authorId": "'"author-dave"'"},
      {"authorId": "'"author-eve"'"}
    ]
  }' \
https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/policyEngine/policyAuthorTrustBundle | jq

B. Richtlinie erstellen (PENDING_ACTIVE)

Definieren Sie eine Richtlinien-ID (ethereum-transfer-policy oder bitcoin-psbt-policy) und erstellen Sie eine Regel, die beschreibt, welche Aktionen zulässig sind. Sie können viele Richtlinien für verschiedene Szenarien konfigurieren. In diesem Leitfaden wird jedoch die Einrichtung einer Richtlinie für Ethereum- oder Bitcoin-Transaktionen beschrieben. Standardmäßig ist die Policy Engine auf Fail-Fast (Block by Default) eingestellt. Wenn eine Transaktion nicht explizit durch eine Richtlinie zugelassen wird, wird sie abgelehnt.

Bevor Autoren eine Richtlinie erstellen oder genehmigen, müssen sie eine Nutzlast signieren, die die Absicht darstellt. Sie können die folgenden Hilfsfunktionen verwenden, um die binäre Serialisierung und Signierung zu verarbeiten.

Ethereum

Diese Regel erlaubt kleinere Ethereum-Überweisungen (≤ 2 ETH, dargestellt als 2.000.000.000 Gwei) an eine bestimmte Adresse für Alice.

1. Quorum-Signatur generieren

Mit Cloud KMS:

sign_policy() {
  local author_id=$1
  local sequence=$2
  local cedar_doc=$3
  local policy_id=$4
  export _HELPER_POLICY_ID="$policy_id"
  export _HELPER_SEQUENCE="$sequence"
  export _HELPER_CEDAR_DOC="$cedar_doc"
  python3 -c "import os; p='my-project'; l='us-central1'; i='mpc-unit-bob-123'; pol=os.environ.get('_HELPER_POLICY_ID'); s=int(os.environ.get('_HELPER_SEQUENCE')); d=os.environ.get('_HELPER_CEDAR_DOC'); n=f'projects/{p}/locations/{l}/instances/{i}/policyEngine/policies/{pol}'; payload=b'action:ACTIVE\n'+n.encode('utf-8')+b'sequence:'+s.to_bytes(8,'little')+b'cedar_document:'+d.encode('utf-8'); open('payload.bin', 'wb').write(payload)"
  gcloud kms asymmetric-sign \
    --version=1 \
    --key="${author_id}-key" \
    --keyring="mpc-assent-keys" \
    --location="us-central1" \
    --digest-algorithm=sha256 \
    --input-file=payload.bin \
    --signature-file=policy_sig.bin > /dev/null
  openssl base64 -A -in policy_sig.bin
}

# Sign as Author 1 (Sequence 0)
QUORUM_SIGNATURE=$(sign_policy "author-dave" 0 'permit(principal == User::"alice", action == Action::"Transfer", resource == Resource::"mpc_operation") when { context.transaction.to_address == "0x1111111111111111111111111111111111111111" && context.transaction.value_gwei <= 2000000000 };' "ethereum-transfer-policy")

Mit OpenSSL:

sign_policy() {
  local author_id=$1
  local sequence=$2
  local cedar_doc=$3
  local policy_id=$4
  export _HELPER_POLICY_ID="$policy_id"
  export _HELPER_SEQUENCE="$sequence"
  export _HELPER_CEDAR_DOC="$cedar_doc"
  python3 -c "import os; p='my-project'; l='us-central1'; i='mpc-unit-bob-123'; pol=os.environ.get('_HELPER_POLICY_ID'); s=int(os.environ.get('_HELPER_SEQUENCE')); d=os.environ.get('_HELPER_CEDAR_DOC'); n=f'projects/{p}/locations/{l}/instances/{i}/policyEngine/policies/{pol}'; payload=b'action:ACTIVE\n'+n.encode('utf-8')+b'sequence:'+s.to_bytes(8,'little')+b'cedar_document:'+d.encode('utf-8'); open('payload.bin', 'wb').write(payload)"
  openssl dgst -sha256 -sign "${author_id}_priv.pem" -out policy_sig.bin payload.bin
  openssl base64 -A -in policy_sig.bin
}

# Sign as Author 1 (Sequence 0)
QUORUM_SIGNATURE=$(sign_policy "author-dave" 0 'permit(principal == User::"alice", action == Action::"Transfer", resource == Resource::"mpc_operation") when { context.transaction.to_address == "0x1111111111111111111111111111111111111111" && context.transaction.value_gwei <= 2000000000 };' "ethereum-transfer-policy")

2. CreatePolicy API aufrufen

# Check existence
if curl -s -f -o /dev/null -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: my-project" \
  "https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/policyEngine/policies/ethereum-transfer-policy"; then
  echo "Policy 'ethereum-transfer-policy' already exists. Skipping creation."
else
  echo "Policy 'ethereum-transfer-policy' not found. Creating..."
  curl -X POST \
    -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
    -H "X-Goog-User-Project: my-project" \
    -H "Content-Type: application/json" \
    -d '{
      "displayName": "Ethereum Transfer Permit Policy",
      "rule": {
        "cedarDocument": "permit(principal == User::\"alice\", action == Action::\"Transfer\", resource == Resource::\"mpc_operation\") when { context.transaction.to_address == \"0x1111111111111111111111111111111111111111\" && context.transaction.value_gwei <= 2000000000 };"
      },
      "createAuthorSignature": {
        "authorId": "'"author-dave"'",
        "signature": "'"$QUORUM_SIGNATURE"'",
        "approvalTime": "'"$(date -u +"%Y-%m-%dT%H:%M:%SZ")"'"
      }
    }' \
  "https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/policyEngine/policies?policy_id=ethereum-transfer-policy" | jq
fi

Bitcoin

Diese Regel erlaubt Bitcoin-Übertragungen mit dem Schema bitcoin.transaction.v2 (BIP 174 PSBT), bei denen der ausgehende Nettoübertragungsbetrag 250.000 Satoshis (context.transaction.spend_amount_scaled <= 2500000 oder 0,0025 BTC,normalisiert auf 10-9 feste Dezimaleinheiten) und die Miner-Gebühr 10.000 Satoshis (context.transaction.max_fee_atomic <= 100000) nicht überschreitet.

permit(
  principal == User::"alice",
  action == Action::"Transfer",
  resource == Resource::"mpc_operation"
)
when {
  context.transaction.schema == "bitcoin.transaction.v2" &&
  context.transaction.spend_amount_scaled <= 2500000 &&
  context.transaction.max_fee_atomic <= 100000
};

1. Quorum-Signatur generieren

Mit Cloud KMS:

sign_policy() {
  local author_id=$1
  local sequence=$2
  local cedar_doc=$3
  local policy_id=$4
  export _HELPER_POLICY_ID="$policy_id"
  export _HELPER_SEQUENCE="$sequence"
  export _HELPER_CEDAR_DOC="$cedar_doc"
  python3 -c "import os; p='my-project'; l='us-central1'; i='mpc-unit-bob-123'; pol=os.environ.get('_HELPER_POLICY_ID'); s=int(os.environ.get('_HELPER_SEQUENCE')); d=os.environ.get('_HELPER_CEDAR_DOC'); n=f'projects/{p}/locations/{l}/instances/{i}/policyEngine/policies/{pol}'; payload=b'action:ACTIVE\n'+n.encode('utf-8')+b'sequence:'+s.to_bytes(8,'little')+b'cedar_document:'+d.encode('utf-8'); open('payload.bin', 'wb').write(payload)"
  gcloud kms asymmetric-sign \
    --version=1 \
    --key="${author_id}-key" \
    --keyring="mpc-assent-keys" \
    --location="us-central1" \
    --digest-algorithm=sha256 \
    --input-file=payload.bin \
    --signature-file=policy_sig.bin > /dev/null
  openssl base64 -A -in policy_sig.bin
}

# Sign as Author 1 (Sequence 0)
QUORUM_SIGNATURE=$(sign_policy "author-dave" 0 'permit(principal == User::"alice", action == Action::"Transfer", resource == Resource::"mpc_operation") when { context.transaction.schema == "bitcoin.transaction.v2" && context.transaction.spend_amount_scaled <= 2500000 && context.transaction.max_fee_atomic <= 100000 };' "bitcoin-psbt-policy")

Mit OpenSSL:

sign_policy() {
  local author_id=$1
  local sequence=$2
  local cedar_doc=$3
  local policy_id=$4
  export _HELPER_POLICY_ID="$policy_id"
  export _HELPER_SEQUENCE="$sequence"
  export _HELPER_CEDAR_DOC="$cedar_doc"
  python3 -c "import os; p='my-project'; l='us-central1'; i='mpc-unit-bob-123'; pol=os.environ.get('_HELPER_POLICY_ID'); s=int(os.environ.get('_HELPER_SEQUENCE')); d=os.environ.get('_HELPER_CEDAR_DOC'); n=f'projects/{p}/locations/{l}/instances/{i}/policyEngine/policies/{pol}'; payload=b'action:ACTIVE\n'+n.encode('utf-8')+b'sequence:'+s.to_bytes(8,'little')+b'cedar_document:'+d.encode('utf-8'); open('payload.bin', 'wb').write(payload)"
  openssl dgst -sha256 -sign "${author_id}_priv.pem" -out policy_sig.bin payload.bin
  openssl base64 -A -in policy_sig.bin
}

# Sign as Author 1 (Sequence 0)
QUORUM_SIGNATURE=$(sign_policy "author-dave" 0 'permit(principal == User::"alice", action == Action::"Transfer", resource == Resource::"mpc_operation") when { context.transaction.schema == "bitcoin.transaction.v2" && context.transaction.spend_amount_scaled <= 2500000 && context.transaction.max_fee_atomic <= 100000 };' "bitcoin-psbt-policy")

2. CreatePolicy API aufrufen

# Check existence
if curl -s -f -o /dev/null -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: my-project" \
  "https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/policyEngine/policies/bitcoin-psbt-policy"; then
  echo "Policy 'bitcoin-psbt-policy' already exists. Skipping creation."
else
  echo "Policy 'bitcoin-psbt-policy' not found. Creating..."
  curl -X POST \
    -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
    -H "X-Goog-User-Project: my-project" \
    -H "Content-Type: application/json" \
    -d '{
      "displayName": "Bitcoin PSBT Permit Policy",
      "rule": {
        "cedarDocument": "permit(principal == User::\"alice\", action == Action::\"Transfer\", resource == Resource::\"mpc_operation\") when { context.transaction.schema == \"bitcoin.transaction.v2\" && context.transaction.spend_amount_scaled <= 2500000 && context.transaction.max_fee_atomic <= 100000 };"
      },
      "createAuthorSignature": {
        "authorId": "'"author-dave"'",
        "signature": "'"$QUORUM_SIGNATURE"'",
        "approvalTime": "'"$(date -u +"%Y-%m-%dT%H:%M:%SZ")"'"
      }
    }' \
  "https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/policyEngine/policies?policy_id=bitcoin-psbt-policy" | jq
fi

C. Richtlinie genehmigen (Übergang zu AKTIV)

Wenn für die Richtlinieneinrichtung M-von-N-Signaturen erforderlich sind, rufen nachfolgende Autoren AddQuorumSignature auf.

Zum Generieren der Signatur müssen Sie zuerst die aktuelle Sequenz der Richtlinie abrufen.

Ethereum

# 1. Get the policy state and sequence
POLICY_INFO=$(curl -s -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: my-project" \
  "https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/policyEngine/policies/ethereum-transfer-policy")

POLICY_STATE=$(echo "$POLICY_INFO" | jq -r '.state // "UNKNOWN"')

if [ "$POLICY_STATE" == "PENDING_ACTIVE" ]; then
  POLICY_SEQUENCE=$(echo "$POLICY_INFO" | jq -r '.globalSequence // 0')
  echo "Policy Sequence: $POLICY_SEQUENCE"

  # 2. Sign as Author 2
  QUORUM_SIGNATURE_SEQ=$(sign_policy "author-eve" $POLICY_SEQUENCE 'permit(principal == User::"alice", action == Action::"Transfer", resource == Resource::"mpc_operation") when { context.transaction.to_address == "0x1111111111111111111111111111111111111111" && context.transaction.value_gwei <= 2000000000 };' "ethereum-transfer-policy")

  # 3. Call AddQuorumSignature
  curl -X POST \
    -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
    -H "X-Goog-User-Project: my-project" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/policyEngine/policies/ethereum-transfer-policy",
      "signature": {
        "authorId": "'"author-eve"'",
        "signature": "'"$QUORUM_SIGNATURE_SEQ"'",
        "approvalTime": "'"$(date -u +"%Y-%m-%dT%H:%M:%SZ")"'"
      }
    }' \
  "https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/policyEngine/policies/ethereum-transfer-policy:addQuorumSignature" | jq
elif [ "$POLICY_STATE" == "ACTIVE" ]; then
  echo "Policy 'ethereum-transfer-policy' is already ACTIVE. Skipping signature."
else
  echo "Policy 'ethereum-transfer-policy' is in an unknown or unexpected state: $POLICY_STATE"
fi

Bitcoin

# 1. Get the policy state and sequence
POLICY_INFO=$(curl -s -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: my-project" \
  "https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/policyEngine/policies/bitcoin-psbt-policy")

POLICY_STATE=$(echo "$POLICY_INFO" | jq -r '.state // "UNKNOWN"')

if [ "$POLICY_STATE" == "PENDING_ACTIVE" ]; then
  POLICY_SEQUENCE=$(echo "$POLICY_INFO" | jq -r '.globalSequence // 0')
  echo "Policy Sequence: $POLICY_SEQUENCE"

  # 2. Sign as Author 2
  QUORUM_SIGNATURE_SEQ=$(sign_policy "author-eve" $POLICY_SEQUENCE 'permit(principal == User::"alice", action == Action::"Transfer", resource == Resource::"mpc_operation") when { context.transaction.schema == "bitcoin.transaction.v2" && context.transaction.spend_amount_scaled <= 2500000 && context.transaction.max_fee_atomic <= 100000 };' "bitcoin-psbt-policy")

  # 3. Call AddQuorumSignature
  curl -X POST \
    -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
    -H "X-Goog-User-Project: my-project" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/policyEngine/policies/bitcoin-psbt-policy",
      "signature": {
        "authorId": "'"author-eve"'",
        "signature": "'"$QUORUM_SIGNATURE_SEQ"'",
        "approvalTime": "'"$(date -u +"%Y-%m-%dT%H:%M:%SZ")"'"
      }
    }' \
  "https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/policyEngine/policies/bitcoin-psbt-policy:addQuorumSignature" | jq
elif [ "$POLICY_STATE" == "ACTIVE" ]; then
  echo "Policy 'bitcoin-psbt-policy' is already ACTIVE. Skipping signature."
else
  echo "Policy 'bitcoin-psbt-policy' is in an unknown or unexpected state: $POLICY_STATE"
fi

Schritt 4: Schlüssel erstellen

Wir erstellen jetzt einen Schlüsselbund und generieren einen verteilten Schlüssel.

A. KeyRing erstellen

Logische Gruppierung für Ihre MPC-Schlüssel.

curl

# Check existence
if curl -s -f -o /dev/null -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: my-project" \
  "https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/mpc/keyRings/my-key-ring"; then
  echo "KeyRing 'my-key-ring' already exists. Skipping creation."
else
  echo "KeyRing 'my-key-ring' not found. Creating..."
  curl -X POST \
    -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
    -H "X-Goog-User-Project: my-project" \
    -H "Content-Type: application/json" \
    -d '{}' \
  https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/mpc/keyRings?key_ring_id=my-key-ring | jq
fi

B. Verteilten Schlüssel generieren (DKG)

Initiieren Sie eine asynchrone Zeremonie, um einen verteilten Schlüssel für alle Teilnehmer zu generieren.

curl

# Check existence
if curl -s -f -o /dev/null -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: my-project" \
  "https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/mpc/keyRings/my-key-ring/keys/my-key"; then
  echo "Key 'my-key' already exists. Skipping generation."
else
  echo "Key 'my-key' not found. Generating..."
  curl -X POST \
    -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
    -H "X-Goog-User-Project: my-project" \
    -H "Content-Type: application/json" \
    -d '{
      "threshold": 2,
      "algorithm": "ECDSA_SECP256K1",
      "participantShares": [
        {"participantId": "projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/mpc/signers/signer-alice"},
        {"participantId": "projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/mpc/signers/signer-bob"},
        {"participantId": "projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/mpc/signers/signer-charlie"}
      ]
    }' \
  https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/mpc/keyRings/my-key-ring/keys?key_id=my-key | jq
fi

Dadurch wird ein Vorgang mit langer Ausführungszeit (Long-Running Operation, LRO) zurückgegeben. Sie müssen warten, bis der Schlüssel state den Status ACTIVE hat. Das bedeutet, dass die DKG-Zeremonie erfolgreich war.

🕵️‍♂️ Schlüsselstatus prüfen (Polling)

Fragen Sie den Schlüsselstatus ab, indem Sie den folgenden Befehl regelmäßig ausführen, bis das Feld state in der Antwort ACTIVE lautet (was bedeutet, dass die DKG-Zeremonie erfolgreich war).

curl

curl -X GET \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: my-project" \
https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/mpc/keyRings/my-key-ring/keys/my-key | jq

C. Blockchain-Adresse ableiten und UTXOs finden

Sobald der Status Ihres verteilten Schlüssels ACTIVE ist, ist der MPC-Schlüssel einsatzbereit. In diesem Schritt leiten Sie die Blockchain-spezifische Adresse aus dem 65-Byte-uncompressed öffentlichen Schlüssel ab, der von der MPC-Schlüsselgenerierung (0x04 || X || Y) zurückgegeben wird, und suchen nach nicht ausgegebenen Mitteln, die für Transaktionen benötigt werden.

Ethereum

Ethereum-Adressen werden aus dem Keccak-256-Hash des unkomprimierten öffentlichen Schlüsselpunkts mit 64 Byte (pub_bytes[1:]) abgeleitet, wobei die letzten 20 Byte verwendet werden.

Address Derivation Script (derive_address.py)

Führen Sie den folgenden Block aus, um derive_address.py zu erstellen und auszuführen:

cat <<'EOF' > derive_address.py
import base64
from eth_utils import keccak, to_checksum_address

# Insert your base64 public_key from Step 4.B
PUBLIC_KEY_B64 = "your-public-key"

pub_bytes = base64.b64decode(PUBLIC_KEY_B64)
if len(pub_bytes) == 65 and pub_bytes[0] == 4:
    raw_addr = "0x" + keccak(pub_bytes[1:])[-20:].hex()
    checksummed_addr = to_checksum_address(raw_addr)
    print("Ethereum Address:", checksummed_addr)
else:
    print(f"Error: Expected 65-byte uncompressed key, got {len(pub_bytes)} bytes.")
EOF
python3 derive_address.py

Ethereum-Adresse mit Guthaben aufladen

Bevor Sie in Schritt 6 die Transaktion übertragen, müssen Sie sicherstellen, dass Ihre abgeleitete Ethereum-Adresse genügend Sepolia-Testnetz-ETH enthält, um den Transaktionswert und die Gas-Kosten zu decken. Sie können kostenlose Testnetz-ETH anfordern, indem Sie Ihre neu abgeleitete Adresse in das Faucet für Ethereum Sepolia eingeben.

Bitcoin

Bitcoin-Adressen werden vom unkomprimierten 65-Byte-secp256k1-EC-öffentlichen Schlüssel (0x04 || X || Y) abgeleitet. Mehrere Adressformate werden vom selben öffentlichen Schlüssel unterstützt:

  1. Native SegWit (P2WPKH / BIP 173): Bech32-Adresse (bc1q... im Mainnet, tb1q... im Testnet4) mit einem 22-Byte-Witness-scriptPubKey (0014<hash160>). Dies ist das empfohlene moderne Format mit den niedrigsten Transaktionsgebühren.
  2. Nested SegWit (P2SH-P2WPKH / BIP 49): Wrapped SegWit-Adresse (3... im Mainnet, 2... im Testnet4) mit einem 23-Byte-scriptPubKey (a914<hash160>87) und einem 22-Byte-RedeemScript (0014<hash160>), kompatibel mit Legacy-Systemen.
  3. Legacy (P2PKH / BIP 44): Originale Pay-to-PubKey-Hash-Base58-Adresse (1... im Mainnet, m/n... im Testnet4) mit einem 25-Byte-scriptPubKey (76a914<hash160>88ac).

Address Derivation Script (derive_btc_address.py)

Führen Sie den folgenden Block aus, um derive_btc_address.py zu erstellen und auszuführen:

cat <<'EOF' > derive_btc_address.py
"""Derives Bitcoin addresses (Native SegWit, Nested SegWit, Legacy) from an MPC public key using embit."""

import base64
from embit import ec, script
from embit.networks import NETWORKS

# Insert the Base64-encoded public key from Step 4.B (publicKey field, decodes to 65 raw bytes)
PUBLIC_KEY_B64 = "your-public-key"

raw_bytes = base64.b64decode(PUBLIC_KEY_B64)
if len(raw_bytes) != 65 or raw_bytes[0] != 4:
  raise ValueError(f"Expected 65-byte uncompressed public key (0x04...), got {len(raw_bytes)} bytes")

# 1. Parse and compress public key to 33 bytes
pub = ec.PublicKey.parse(raw_bytes)
pub.compressed = True
compressed_pubkey_hex = pub.sec().hex()

print("================================================================")
print(f"Compressed Public Key (33 bytes): {compressed_pubkey_hex}")
print("================================================================")

# 2. Native SegWit (P2WPKH - BIP 173)
p2wpkh = script.p2wpkh(pub)
print("\n[1. Native SegWit (P2WPKH / Bech32 - BIP 173)]")
print(f"  ScriptPubKey:           {p2wpkh.data.hex()}")
print(f"  Testnet4 Address (tb):  {p2wpkh.address(NETWORKS['test'])}")
print(f"  Mainnet Address (bc):   {p2wpkh.address(NETWORKS['main'])}")

# 3. Nested SegWit (P2SH-P2WPKH - BIP 49)
p2sh_p2wpkh = script.p2sh(p2wpkh)
print("\n[2. Nested SegWit (P2SH-P2WPKH / Base58 - BIP 49)]")
print(f"  RedeemScript:           {p2wpkh.data.hex()}")
print(f"  ScriptPubKey:           {p2sh_p2wpkh.data.hex()}")
print(f"  Testnet4 Address (2):   {p2sh_p2wpkh.address(NETWORKS['test'])}")
print(f"  Mainnet Address (3):    {p2sh_p2wpkh.address(NETWORKS['main'])}")

# 4. Legacy (P2PKH - BIP 44)
p2pkh = script.p2pkh(pub)
print("\n[3. Legacy (P2PKH / Base58 - BIP 44)]")
print(f"  ScriptPubKey:           {p2pkh.data.hex()}")
print(f"  Testnet4 Address (m/n): {p2pkh.address(NETWORKS['test'])}")
print(f"  Mainnet Address (1):    {p2pkh.address(NETWORKS['main'])}")
EOF

python3 derive_btc_address.py

Nicht ausgegebene Ausgaben (UTXOs) finden

Im Gegensatz zu kontobasierten Blockchains werden bei Bitcoin-Transaktionen bestimmte nicht ausgegebene Transaktionsausgaben (Unspent Transaction Outputs, UTXOs) ausgegeben. Bevor Sie in Schritt 5 eine Transaktion erstellen:

  1. Zahlen Sie Guthaben auf Ihre abgeleitete Native SegWit-Adresse ein (z. B. über ein Testnetz-Faucet).
  2. Fragen Sie die nicht ausgegebenen UTXOs Ihrer Adresse mit Ihrem Bitcoin-Knoten (bitcoin-cli listunspent 1 9999999 "[\"tb1q...\"]") oder einer Block-Explorer-API ab.
  3. Beachten Sie die folgenden vier Parameter für Schritt 5:
    • txid: Der 32-Byte-Transaktions-Hash der nicht ausgegebenen Ausgabe.
    • vout: Der nullbasierte Ausgabeindex (z. B. 0 oder 1).
    • value: Der nicht ausgegebene Betrag in Satoshis (z. B. 200000 Satoshis = 0, 002 BTC).
    • scriptPubKey: Der Hexadezimalwert des Sperrskripts (z. B. 0014...).

Schritt 5: MPC-Vorgang ausführen

Führen Sie einen MPC-Vorgang aus, indem Sie eine Transaktion bewerten und einen Vorschlag erstellen.

A. Transaktion bewerten (Autorisierungstoken abrufen)

Bevor Sie einen MPC-Vorgang signieren, müssen Sie die Nutzlast anhand Ihrer Richtlinien autorisieren, um ein JWS-Token und den 32-Byte-Signatur-Digest zu erhalten.

Ethereum

🛑 Fall A: Ablehnung (zu hoher Wert)

Versuch, eine Transaktion von 3 ETH zu autorisieren (übersteigt unser Limit von 2 ETH).

# Payload for 3 ETH (Hex equivalent contains value_gwei > 2B)
RAW_TX_BYTES_HEX="ef808504a817c8008252089411111111111111111111111111111111111111118829a2241af62c00008083aa36a78080"

curl -X POST \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: my-project" \
  -H "Content-Type: application/json" \
  -d '{
    "principal": "User::\"alice\"",
    "action": "Action::\"Transfer\"",
    "resource": "Resource::\"mpc_operation\"",
    "schema": "ETHEREUM_TX_V1",
    "payload": "'"$RAW_TX_BYTES_HEX"'"
  }' \
https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/policyEngine:authorizePayload | jq

Erwartete Antwort: decision: DENY

✅ Fall B: Genehmigung (zulässiger Wert)

Versuchen Sie, eine Transaktion von 1 Gwei zu autorisieren (innerhalb unseres Limits von 2 ETH).

# Payload for 1 Gwei
RAW_TX_BYTES_HEX="eb808504a817c800825208941111111111111111111111111111111111111111843b9aca008083aa36a78080"

RESPONSE=$(curl -s -X POST \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: my-project" \
  -H "Content-Type: application/json" \
  -d '{
    "principal": "User::\"alice\"",
    "action": "Action::\"Transfer\"",
    "resource": "Resource::\"mpc_operation\"",
    "schema": "ETHEREUM_TX_V1",
    "payload": "'"$RAW_TX_BYTES_HEX"'"
  }' \
https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/policyEngine:authorizePayload)

echo "$RESPONSE" | jq

# Automatically capture variables for next steps
DIGEST_HEX=$(echo "$RESPONSE" | jq -r .digest)
POLICY_ENGINE_TOKEN=$(echo "$RESPONSE" | jq -r .token)

# Convert to Base64 (which curl expects for bytes fields)
DIGEST_B64=$(python3 -c "import base64; print(base64.b64encode(bytes.fromhex('$DIGEST_HEX')).decode('utf-8'))")
POLICY_ENGINE_TOKEN_B64=$(python3 -c "import base64; print(base64.b64encode('$POLICY_ENGINE_TOKEN'.encode('utf-8')).decode('utf-8'))")

Erwartete Antwort: decision: PERMIT zusammen mit einem token.

Bitcoin

Im Bitcoin-UTXO-Modell werden bei Transaktionen bestimmte nicht ausgegebene Ausgaben ausgegeben. In SegWit (BIP 143) müssen für die Berechnung des Signatur-Hashs der Satoshi-Betrag und das Sperrskript des ausgegebenen Inputs bekannt sein.

Die Policy Engine erfordert das BIP 174 PSBT v0-Format (BITCOIN_TX_V2), wobei PSBT_IN_WITNESS_UTXO für jede Eingabe ausgefüllt sein muss.

PSBT-Konstruktionsskript (build_psbt.py)

Führen Sie den folgenden Block aus, um eine gültige BIP 174 PSBT v0-Nutzlast zusammenzustellen:

cat <<'EOF' > build_psbt.py
"""Constructs a BIP 174 PSBT v0 payload using embit for Bitcoin policy evaluation."""

from embit import script, transaction
from embit.psbt import PSBT

# 1. Inputs (From Step 4.C)
PREV_TXID_HEX = "8f594f33156993f1c980c2273dcae4a92cf2517fbc29264421395e63a63916d8"
PREV_VOUT = int("1")
INPUT_VALUE_SATS = int("200000")  # Total UTXO input value in satoshis
SENDER_SCRIPTPUBKEY_HEX = "0014eced05cd51d964102a20c856b09080ad620e9da2"

# 2. Output Allocations
RECIPIENT_SATS = int("50000")  # Recipient payment in satoshis
MINER_FEE_SATS = int("250")  # Miner fee in satoshis
RECIPIENT_SCRIPTPUBKEY_HEX = "0014751e76e8199196d454941c45d1b3a323f1433bd6"

# Change Output calculation: InputSats - RecipientSats - MinerFeeSats
CHANGE_SATS = INPUT_VALUE_SATS - RECIPIENT_SATS - MINER_FEE_SATS
if CHANGE_SATS <= 0:
  raise ValueError(f"Insufficient input satoshis ({INPUT_VALUE_SATS}) for payment and fee")

# 3. Construct Unsigned Transaction (Version 2, 1 input, 2 outputs, locktime 0)
tx = transaction.Transaction(
    version=2,
    vin=[transaction.TransactionInput(
        txid=bytes.fromhex(PREV_TXID_HEX),
        vout=PREV_VOUT,
        sequence=0xFFFFFFFF,
    )],
    vout=[
        transaction.TransactionOutput(
            value=RECIPIENT_SATS,
            script_pubkey=script.Script(bytes.fromhex(RECIPIENT_SCRIPTPUBKEY_HEX)),
        ),
        transaction.TransactionOutput(
            value=CHANGE_SATS,
            script_pubkey=script.Script(bytes.fromhex(SENDER_SCRIPTPUBKEY_HEX)),
        ),
    ],
    locktime=0,
)

# 4. Package into BIP 174 PSBT with Input Witness UTXO
psbt = PSBT(tx)
psbt.inputs[0].witness_utxo = transaction.TransactionOutput(
    value=INPUT_VALUE_SATS,
    script_pubkey=script.Script(bytes.fromhex(SENDER_SCRIPTPUBKEY_HEX)),
)

print(psbt.serialize().hex())
EOF

BTC_PSBT_HEX=$(python3 build_psbt.py)
echo "Generated PSBT Hex (${#BTC_PSBT_HEX} chars):"
echo "$BTC_PSBT_HEX"

🛑 Fall A: Ablehnung (Gebührenlimit überschritten)

Versuch, eine Transaktion zu autorisieren, bei der die Miner-Gebühr das Richtlinienlimit von 10.000 Satoshi überschreitet. In diesem Beispiel wird durch das Festlegen von MINER_FEE_SATS = 15000 (und CHANGE_SATS = 135000) in build_psbt.py auf max_fee_atomic = 150000 skaliert (was die Richtlinienobergrenze von 100000 überschreitet). Dadurch wird die folgende Nutzlast erzeugt, die eine DENY-Entscheidung der Policy Engine auslöst.

# Construct a PSBT payload with miner_fee_sats = 15000 -> max_fee_atomic = 150000 (exceeds 100000 limit)
REJECTED_PSBT_HEX="70736274ff0100710200000001d81639a6635e3921442629bc7f51f22ca9e4ca3d27c280c9f1936915334f598f0100000000ffffffff0250c3000000000000160014751e76e8199196d454941c45d1b3a323f1433bd6580f020000000000160014eced05cd51d964102a20c856b09080ad620e9da2000000000001011f400d030000000000160014eced05cd51d964102a20c856b09080ad620e9da2000000"

curl -X POST \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: my-project" \
  -H "Content-Type: application/json" \
  -d '{
    "principal": "User::\"alice\"",
    "action": "Action::\"Transfer\"",
    "resource": "Resource::\"mpc_operation\"",
    "schema": "BITCOIN_TX_V2",
    "payload": "'"$REJECTED_PSBT_HEX"'"
  }' \
https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/policyEngine:authorizePayload | jq

Erwartete Antwort: decision: DENY

✅ Fall B: Genehmigung (zulässige Bitcoin-PSBT)

Versuchen Sie, die zuvor erstellte gültige PSBT zu autorisieren (RECIPIENT_SATS = 50000 wird spend_amount_scaled = 500000 <= 2500000 zugeordnet und MINER_FEE_SATS = 250 wird max_fee_atomic = 2500 <= 100000 zugeordnet).

RESPONSE=$(curl -s -X POST \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: my-project" \
  -H "Content-Type: application/json" \
  -d '{
    "principal": "User::\"alice\"",
    "action": "Action::\"Transfer\"",
    "resource": "Resource::\"mpc_operation\"",
    "schema": "BITCOIN_TX_V2",
    "payload": "'"$BTC_PSBT_HEX"'"
  }' \
https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/policyEngine:authorizePayload)

echo "$RESPONSE" | jq

# Automatically capture variables for next steps
DIGEST_HEX=$(echo "$RESPONSE" | jq -r .digest)
POLICY_ENGINE_TOKEN=$(echo "$RESPONSE" | jq -r .token)

# Convert to Base64 (which curl expects for bytes fields)
DIGEST_B64=$(python3 -c "import base64; print(base64.b64encode(bytes.fromhex('$DIGEST_HEX')).decode('utf-8'))")
POLICY_ENGINE_TOKEN_B64=$(python3 -c "import base64; print(base64.b64encode('$POLICY_ENGINE_TOKEN'.encode('utf-8')).decode('utf-8'))")

Erwartete Antwort: decision: PERMIT zusammen mit einem JWS token und einem 32-Byte-BIP-143-Sighash digest.

Dadurch wird Folgendes zurückgegeben:

  • decision: PERMIT oder DENY
  • token: Das JWS-Richtlinien-Token (Nachweis der Compliance).
  • digest: Der hexadezimal codierte 32-Byte-Signatur-Digest.
  • evaluatedPolicyIds: Richtlinien, die ausgewertet wurden und die Entscheidung beeinflusst haben.

B. Angebot erstellen (Unterzeichnung einleiten)

Bieten Sie die Digest-Nutzlast zum Signieren an und hängen Sie das in Schritt 5A erhaltene Policy Engine-Token an.

curl

# Check existence
if curl -s -f -o /dev/null -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: my-project" \
  "https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/mpc/keyRings/my-key-ring/keys/my-key/proposals/my-proposal"; then
  echo "Proposal 'my-proposal' already exists. Skipping creation."
else
  echo "Proposal 'my-proposal' not found. Creating..."
  curl -X POST \
    -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
    -H "X-Goog-User-Project: my-project" \
    -H "Content-Type: application/json" \
    -d '{
      "digest": "'"$DIGEST_B64"'",
      "keyVersion": "projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/mpc/keyRings/my-key-ring/keys/my-key/versions/1",
      "initiator": "projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/mpc/signers/signer-alice",
      "policyApproverSignature": "'"$POLICY_ENGINE_TOKEN_B64"'"
    }' \
  https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/mpc/keyRings/my-key-ring/keys/my-key/proposals?proposal_id=my-proposal | jq
fi

Dadurch wird ein Vorgang mit langer Ausführungszeit (Long-Running Operation, LRO) zurückgegeben. Der erwartete state-Wert des Angebots ist PENDING_ASSENT (was bedeutet, dass es von den Teilnehmern unterzeichnet werden kann).

🕵️‍♂️ Angebotsstatus prüfen (Polling)

Fragen Sie den Status des Vorschlags regelmäßig mit dem folgenden Befehl ab.

curl

curl -X GET \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: my-project" \
https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/mpc/keyRings/my-key-ring/keys/my-key/proposals/my-proposal | jq

C. Entscheidungen der Teilnehmer aufzeichnen (Zustimmung)

Die Teilnehmer müssen den Vorschlagshash mit ihren lokalen privaten Schlüsseln (Schlüsselfreigaben) signieren und die Zustimmung weiterleiten.

1. Zusammenfassung des Angebots zur Signatur

Cloud KMS

sign_digest() {
  local signer_id=$1
  local digest_hex=$2

  local digest_b64=$(python3 -c "import base64, sys; print(base64.b64encode(bytes.fromhex('$digest_hex')).decode('utf-8'))")

  # Direct API calls are used here because the input is already a pre-computed 32-byte digest from the MPC.
  # Tools like gcloud assume input is raw data and apply a redundant hashing layer, altering the payload.
  # Unlike Policy signings which hash raw administrative text, Proposal signatures must sign this exact 32-byte hash as-is.
  curl -s -X POST \
    -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
    -H "Content-Type: application/json" \
    -d "{\"digest\": {\"sha256\": \"$digest_b64\"}}" \
  https://cloudkms.googleapis.com/v1/projects/my-project/locations/us-central1/keyRings/mpc-assent-keys/cryptoKeys/${signer_id}-key/cryptoKeyVersions/1:asymmetricSign | jq -r .signature
}

SIGNER_2_SIG_B64=$(sign_digest "signer-bob" "$DIGEST_HEX")
SIGNER_3_SIG_B64=$(sign_digest "signer-charlie" "$DIGEST_HEX")

OpenSSL

sign_digest() {
  local signer_id=$1
  local digest_hex=$2

  python3 -c "import sys; sys.stdout.buffer.write(bytes.fromhex('$digest_hex'))" > digest.bin

  openssl pkeyutl -sign -inkey "${signer_id}_priv.pem" -in digest.bin -out digest_sig.bin

  openssl base64 -A -in digest_sig.bin
}

SIGNER_2_SIG_B64=$(sign_digest "signer-bob" "$DIGEST_HEX")
SIGNER_3_SIG_B64=$(sign_digest "signer-charlie" "$DIGEST_HEX")

2. Entscheidung aufzeichnen

curl

# 1. Check state for Signer 2
PROPOSAL_INFO=$(curl -s -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: my-project" \
  "https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/mpc/keyRings/my-key-ring/keys/my-key/proposals/my-proposal")

PROPOSAL_STATE=$(echo "$PROPOSAL_INFO" | jq -r '.state // "UNKNOWN"')

if [ "$PROPOSAL_STATE" == "PENDING_ASSENT" ]; then
  echo "Recording assent for Signer 2..."
  curl -X POST \
    -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
    -H "X-Goog-User-Project: my-project" \
    -H "Content-Type: application/json" \
    -d '{
      "signature": "'"$SIGNER_2_SIG_B64"'",
      "decision": "ASSENT",
      "participant": "signer-bob"
    }' \
  https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/mpc/keyRings/my-key-ring/keys/my-key/proposals/my-proposal:recordParticipantDecision | jq
elif [ "$PROPOSAL_STATE" == "READY" ] || [ "$PROPOSAL_STATE" == "SUCCEEDED" ]; then
  echo "Proposal is in state $PROPOSAL_STATE. Skipping Signer 2."
else
  echo "Proposal is in an unknown or unexpected state: $PROPOSAL_STATE"
fi

# 2. Re-check state for Signer 3 (Signer 2 might have triggered a state change)
PROPOSAL_INFO=$(curl -s -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: my-project" \
  "https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/mpc/keyRings/my-key-ring/keys/my-key/proposals/my-proposal")

PROPOSAL_STATE=$(echo "$PROPOSAL_INFO" | jq -r '.state // "UNKNOWN"')

if [ "$PROPOSAL_STATE" == "PENDING_ASSENT" ]; then
  echo "Recording assent for Signer 3..."
  curl -X POST \
    -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
    -H "X-Goog-User-Project: my-project" \
    -H "Content-Type: application/json" \
    -d '{
      "signature": "'"$SIGNER_3_SIG_B64"'",
      "decision": "ASSENT",
      "participant": "signer-charlie"
    }' \
  https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/mpc/keyRings/my-key-ring/keys/my-key/proposals/my-proposal:recordParticipantDecision | jq
elif [ "$PROPOSAL_STATE" == "READY" ] || [ "$PROPOSAL_STATE" == "SUCCEEDED" ]; then
  echo "Proposal is in state $PROPOSAL_STATE. Skipping Signer 3."
else
  echo "Proposal is in an unknown or unexpected state: $PROPOSAL_STATE"
fi

D. Endgültige Signatur abrufen

Sobald die erforderliche Anzahl an Zustimmungen erreicht ist, können Sie den Status des Angebots prüfen oder die Signatur abrufen.

curl

curl -X GET \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: my-project" \
https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/mpc/keyRings/my-key-ring/keys/my-key/proposals/my-proposal | jq

Wenn das Angebot den Status SUCCEEDED hat, sehen Sie sich das Feld finalSignature in der zurückgegebenen JSON-Datei an.


Schritt 6: Transaktion an die Blockchain übertragen

Sobald Ihr Angebot den Status SUCCEEDED erreicht hat und Sie die finalSignature abgerufen haben, müssen Sie im letzten Schritt die signierte Überweisungstransaktion erstellen und an das Netzwerk senden.

Ethereum

Ableitung und Übertragung von Wiederherstellungs-IDs

Die MPC API gibt eine unformatierte 64-Byte-ECDSA-Signatur zurück, die die Werte r und s enthält. Der Server stellt sicher, dass die Signatur im kanonischen Low-S-Format (EIP-2) vorliegt. Ethereum erfordert jedoch eine Wiederherstellungs-ID (v), um die Adresse des Absenders abzuleiten und die Signatur zu verifizieren. Diese wird in standardmäßigen 64-Byte-MPC-Signaturen nicht direkt zurückgegeben.

Das folgende Python-Script übernimmt diese Komplexität. Es ermittelt dynamisch das richtige Wiederherstellungsbit, indem es die erwartete Absenderadresse abgleicht, erstellt die gültige Transaktion und sendet sie an das Netzwerk.

Skript für die Nachricht an alle:

Führen Sie den folgenden Block in Ihrem Terminal aus, um broadcast_eth.py zu schreiben und auszuführen:

cat <<'EOF' > broadcast_eth.py
"""Broadcasts an Ethereum transaction using a normalized MPC signature."""

import base64
import os
import requests
from eth_account import Account
try:
    from eth_account._utils.transactions import encode_transaction, serializable_unsigned_transaction_from_dict
except ImportError:
    from eth_account._utils.legacy_transactions import encode_transaction, serializable_unsigned_transaction_from_dict

# ==========================================
# 1. INPUTS
# ==========================================
FINAL_SIGNATURE_B64 = "your-final-signature"
RPC_URL = "https://ethereum-sepolia-rpc.publicnode.com"
EXPECTED_SENDER = "0xyour-mpc-key-address"

if not FINAL_SIGNATURE_B64 or FINAL_SIGNATURE_B64 == "your-final-signature":
    raise ValueError("FINAL_SIGNATURE_B64 is not set. Please provide a valid signature.")

# ==========================================
# 2. TRANSACTION PARAMETERS
# ==========================================
CHAIN_ID = 11155111  # Sepolia Testnet
tx_dict = {
    "nonce": int("0"),
    "gasPrice": int("20000000000"),  # 20 Gwei
    "gas": int("21000"),
    "to": "0x1111111111111111111111111111111111111111",
    "value": int("1000000000"),  # 1 Gwei
    "data": b"",
    "chainId": CHAIN_ID,
}

# ==========================================
# 3. DECODE & SPLIT SIGNATURE
# ==========================================
missing_padding = len(FINAL_SIGNATURE_B64) % 4
if missing_padding:
    FINAL_SIGNATURE_B64 += "=" * (4 - missing_padding)

sig_bytes = base64.b64decode(FINAL_SIGNATURE_B64)
r = int.from_bytes(sig_bytes[:32], byteorder="big")
s = int.from_bytes(sig_bytes[32:], byteorder="big")

# ==========================================
# 4. RECOVERY IDENTIFIER (V) DERIVATION
# ==========================================
chain_v_base = (CHAIN_ID * 2) + 35

# ==========================================
# 5. GENERATE UNSIGNED TX & RECOVER SENDER ADDRESS
# ==========================================
unsigned_tx = serializable_unsigned_transaction_from_dict(tx_dict)

derived_sender = None
encoded_tx = None
final_v = chain_v_base

EXPECTED_SENDER_PLACEHOLDER = "0xyour-mpc-key-address"
is_valid_expected = EXPECTED_SENDER and EXPECTED_SENDER != EXPECTED_SENDER_PLACEHOLDER

for parity in [0, 1]:
    try_v = chain_v_base + parity
    try:
        tx_candidate = encode_transaction(unsigned_tx, vrs=(try_v, r, s))
        addr = Account.recover_transaction("0x" + tx_candidate.hex())

        if is_valid_expected and EXPECTED_SENDER.lower() == addr.lower():
            derived_sender = addr
            final_v = try_v
            encoded_tx = tx_candidate
            break

        if not is_valid_expected and derived_sender is None:
            derived_sender = addr
            final_v = try_v
            encoded_tx = tx_candidate
            print(f"Warning: EXPECTED_SENDER not provided or is placeholder. Assuming first recovered address: {addr}")

    except Exception:
        continue

if encoded_tx is None:
    if is_valid_expected:
        raise ValueError(f"Could not recover address matching EXPECTED_SENDER: {EXPECTED_SENDER}")
    else:
        raise ValueError("Could not derive broadcastable transaction. Ensure signature matches payload.")

if derived_sender:
    print(f"Derived Sender Address: {derived_sender}")
else:
    print("Derived Sender Address: Could not recover (ensure signature matches current transaction payload)")

valid_tx_hex = "0x" + encoded_tx.hex()
print(f"Broadcastable Transaction Hex:\n{valid_tx_hex}\n")

# ==========================================
# 6. BROADCAST TO NETWORK
# ==========================================
payload = {
    "jsonrpc": "2.0",
    "method": "eth_sendRawTransaction",
    "params": [valid_tx_hex],
    "id": 1,
}

print(f"Broadcasting to {RPC_URL}...")
response = requests.post(RPC_URL, json=payload)
resp_json = response.json()
print("RPC Response:", resp_json)
if "result" in resp_json:
    tx_hash = resp_json["result"]
    print(f"View transaction on Sepolia Etherscan: https://sepolia.etherscan.io/tx/{tx_hash}")
EOF

python3 broadcast_eth.py

Häufige Fehler beheben

  • invalid sender: recovery failed oder invalid transaction v, r, s values: Dieser Fehler tritt auf, wenn die Transaktionsparameter in Ihrem Skript nicht genau mit der signierten Nutzlast übereinstimmen. Achten Sie darauf, dass die Felder value, nonce und to mit den in Schritt 5 verarbeiteten Feldern übereinstimmen.
  • insufficient funds for gas * price + value: Ihre Transaktion ist kryptografisch gültig und wurde vom Knoten akzeptiert, aber die Ethereum-Adresse Ihres MPC-Schlüssels hat nicht genügend Testnetz-ETH, um den Vorgang abzudecken. Sie müssen Ihre Adresse vor dem Senden mit Guthaben aufladen.

Bitcoin

SegWit-Wire-Transaktionserstellung und -übertragung

Bei Bitcoin Native SegWit-Transaktionen (BIP 141) muss die unformatierte 64-Byte-(r, s)-MPC-Signatur in kanonisches Low-S-ASN.1-DER formatiert, mit 0x01 (SIGHASH_ALL) angehängt und mit dem komprimierten öffentlichen 33-Byte-Schlüssel in den SegWit-Witness-Stack mit zwei Elementen eingefügt werden: [DER signature || 0x01, compressed public key].

Skript für die Nachricht an alle:

Führen Sie den folgenden Block aus, um broadcast_btc.py zu erstellen und auszuführen:

cat <<'EOF' > broadcast_btc.py
"""Assembles a signed Bitcoin Native SegWit transaction from an MPC signature using embit and broadcasts it."""

import base64
import urllib.request
from embit import ec, script, transaction

# ==========================================
# 1. INPUTS
# ==========================================
FINAL_SIGNATURE_B64 = "your-final-signature"
COMPRESSED_PUBKEY_HEX = "03deb76948c14003446a8c1d79e88fb3607c9bbc1f5143b186aa7b61d6b1facc36"

PREV_TXID_HEX = "8f594f33156993f1c980c2273dcae4a92cf2517fbc29264421395e63a63916d8"
PREV_VOUT = int("1")

RECIPIENT_SATS = int("50000")
RECIPIENT_SCRIPTPUBKEY_HEX = "0014751e76e8199196d454941c45d1b3a323f1433bd6"

CHANGE_SATS = int("149750")
CHANGE_SCRIPTPUBKEY_HEX = "0014eced05cd51d964102a20c856b09080ad620e9da2"

BROADCAST_API_URL = "https://mempool.space/testnet4/api/tx"

# ==========================================
# 2. CONVERT RAW (r, s) SIGNATURE TO CANONICAL LOW-S ASN.1 DER + SIGHASH_ALL
# ==========================================
sig_bytes = base64.b64decode(FINAL_SIGNATURE_B64)
internal_sig = ec.secp256k1.ecdsa_signature_parse_compact(sig_bytes)
ec.secp256k1.ecdsa_signature_normalize(internal_sig, internal_sig)
der_sig = ec.secp256k1.ecdsa_signature_serialize_der(internal_sig) + b"\x01"  # 0x01 = SIGHASH_ALL

# ==========================================
# 3. BUILD TRANSACTION & ATTACH SEGWIT WITNESS STACK
# ==========================================
tx = transaction.Transaction(
    version=2,
    vin=[transaction.TransactionInput(
        txid=bytes.fromhex(PREV_TXID_HEX),
        vout=PREV_VOUT,
        sequence=0xFFFFFFFF,
    )],
    vout=[
        transaction.TransactionOutput(
            value=RECIPIENT_SATS,
            script_pubkey=script.Script(bytes.fromhex(RECIPIENT_SCRIPTPUBKEY_HEX)),
        ),
        transaction.TransactionOutput(
            value=CHANGE_SATS,
            script_pubkey=script.Script(bytes.fromhex(CHANGE_SCRIPTPUBKEY_HEX)),
        ),
    ],
    locktime=0,
)

pubkey_bytes = bytes.fromhex(COMPRESSED_PUBKEY_HEX)
tx.vin[0].witness = transaction.Witness([der_sig, pubkey_bytes])

signed_tx_hex = tx.serialize().hex()
print("--- Signed Bitcoin SegWit Wire Transaction Hex ---")
print(signed_tx_hex)

# ==========================================
# 4. BROADCAST TO NETWORK
# ==========================================
if BROADCAST_API_URL:
  print(f"\nBroadcasting to {BROADCAST_API_URL}...")
  try:
    req = urllib.request.Request(
        BROADCAST_API_URL,
        data=signed_tx_hex.encode("utf-8"),
        headers={"Content-Type": "text/plain"},
        method="POST",
    )
    with urllib.request.urlopen(req) as resp:
      txid_result = resp.read().decode("utf-8").strip()
      print(f"✅ Broadcast Successful! Transaction ID (txid): {txid_result}")
      print(f"Track in Mempool Explorer: https://mempool.space/testnet4/tx/{txid_result}")
  except Exception as e:
    print(f"Broadcast response: {e}")
EOF

python3 broadcast_btc.py

Häufige Fehler beheben

  • mandatory-script-verify-flag-failed (Signature must be zero for failed CHECK(MULTI)SIG operation) / non-mandatory-script-verify-flag (Non-canonical DER signature): Die s-Komponente der Signatur muss im kanonischen Low-S-Format (s <= N/2) und ordnungsgemäß DER-codiert sein.
  • bad-txns-in-belowout / bad-txns-fee-negative: Die Gesamtausgaben übersteigen den Eingabewert. Die Änderungsberechnung (InputSatoshis - PaymentSatoshis - MinerFee) muss strikt positiv sein.

📖 Anhang: Betriebshandbuch (Abfragen und Prüfen)

Mit diesen Befehlen mit Lesezugriff können Sie den Status Ihrer Ressourcen jederzeit prüfen.


👥 1. Teilnehmer und Signaturgeber

Prüfen Sie die Teilnehmer und Signierer in Ihrer Instanz.

A. Alle registrierten Teilnehmer auflisten

curl

curl -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: my-project" \
https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/mpc/signers | jq

B. Details zu einem bestimmten Teilnehmer abrufen

curl

curl -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: my-project" \
https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/mpc/signers/signer-alice | jq

📜 2. Policy Engine

Richtlinien und Trust Bundles prüfen

A. Policy Author Trust Bundle abrufen

curl

curl -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: my-project" \
https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/policyEngine/policyAuthorTrustBundle | jq

B. Alle Richtlinien auflisten

curl

curl -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: my-project" \
https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/policyEngine/policies | jq

C. Details zu einer bestimmten Richtlinie abrufen

curl

curl -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: my-project" \
https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/policyEngine/policies/ethereum-transfer-policy | jq

🔑 3. Schlüssel und Schlüsselbunde

Schlüsselbunde und Schlüssel prüfen

A. Alle Schlüsselbunde auflisten

curl

curl -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: my-project" \
https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/mpc/keyRings | jq

B. Alle Schlüssel in einem Schlüsselbund auflisten

curl

curl -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: my-project" \
https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/mpc/keyRings/my-key-ring/keys | jq

C. Status eines bestimmten verteilten Schlüssels abrufen (DKG-Status)

curl

curl -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: my-project" \
https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/mpc/keyRings/my-key-ring/keys/my-key | jq

📝 4. Angebote (Signaturvorgänge)

Angebote und Signaturstatus prüfen

A. Alle Vorschläge für einen Schlüssel auflisten

curl

curl -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: my-project" \
https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/mpc/keyRings/my-key-ring/keys/my-key/proposals | jq

B. Details oder Status eines bestimmten Angebots abrufen

curl

curl -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "X-Goog-User-Project: my-project" \
https://67317436-0f29-ba9e-9146-d11c0bd8df9b.us-central1.mpc.goog/v1alpha1/projects/my-project/locations/us-central1/instances/mpc-unit-bob-123/mpc/keyRings/my-key-ring/keys/my-key/proposals/my-proposal | jq