במדריך הזה מוסבר איך להשתמש ב-Multi-party Compute API כדי לנהל מפתחות, משתתפים וכללי מדיניות מבוזרים. הוא מיועד ללקוחות שמשתמשים בגרסת טרום-השקה (Preview) ולומדים את התהליכים המרכזיים שעוברים המשתמשים.
סקירה כללית
MPC API מורכב משני רכיבים עיקריים:
- Core MPC מנהל את מחזור החיים של מפתחות מבוזרים, הצעות וטקסי חתימה.
- מנגנון המדיניות אוכף את הפיקוח ומאשר פעולות לפני שהן מבוצעות.
במאמר אבטחה והרשאה מוסבר בפירוט איך הגישה ל-API והחתימה הקריפטוגרפית פועלות יחד.
דרישות מוקדמות
לפני שמתחילים, חשוב לוודא שיש לכם:
- Google Cloud פרויקט. כדי ליצור פרויקט, קראו את המאמר יצירת פרויקטים.
- אם לא משתמשים ב-Cloud Shell, צריך להתקין את ה-CLI של
gcloudולאמת אותו. ממשקי ה-API הנדרשים מופעלים, אם הם עדיין לא הופעלו. אתם צריכים שני ממשקי API: ממשק ה-MPC API החדש שבו משתמשים במדריך הזה, ו-Cloud Key Management Service אם אתם משתמשים בו ליצירת מפתחות הסכמה או לחתימה עליהם.
gcloud services enable mpc.googleapis.com cloudkms.googleapis.comכדי לראות את ממשקי ה-API שמופעלים:
gcloud services list --enabledPython 3.9 ואילך וספריות עזר למדריכים מותקנות:
pip install embit eth-account eth-utils requests(אופציונלי) אם הארגון שלכם מבצע אימות באמצעות ספק זהויות חיצוני (כמו Okta או Microsoft Entra ID) בלי חשבונות Google, אתם יכולים לעיין במאמר הגדרת איחוד שירותי אימות הזהות של כוח עבודה (BYOID) כדי להגדיר את Application Default Credentials (ADC) עם הזהות המאוחדת שלכם.
הגדרה
מעדכנים את המשתנים הבאים כדי לאכלס את הפקודות במדריך הזה.
ההגדרה הנדרשת
חובה להתאים אישית את הפרמטרים האלה. המדריך לא יפעל עם ערכי ה-placeholder.
| פרמטר | ערך | תיאור |
|---|---|---|
| מזהה פרויקט | my-project |
מזהה הפרויקט של הצרכן שסיפקתם לנו כדי להצטרף לגרסת טרום-ההשקה (Preview). |
| מזהה הדומיין | 67317436-0f29-ba9e-9146-d11c0bd8df9b |
מזהה הדומיין הייחודי שהקצינו למכונה שלכם. |
| מזהה נתיב המופע | mpc-unit-bob-123 |
מזהה המכונה של הנתיב שהקצינו למכונה שלכם. |
| Location | us-central1 |
מיקום הפריסה. חייב להיות us-central1 (אזורים אחרים לא נתמכים בגרסת טרום-השקה). |
תוויות למשאבי הדרכה
אלה שמות של משאבים שנוצרו במהלך המדריך הזה. ערכי ברירת המחדל תקינים ואפשר להשאיר אותם כדי לצמצם את החיכוך בהגדרה.
| פרמטר | ערך | תיאור |
|---|---|---|
| מזהה אוסף המפתחות ב-Cloud KMS | mpc-assent-keys |
אוסף מפתחות של Cloud Key Management Service למפתחות הסכמה חיצוניים שמאוחסנים ב-Cloud KMS. |
| מזהה של בעל החתימה הראשון | signer-alice |
MPC Signer 1. |
| מזהה בעל החתימה השני | signer-bob |
MPC Signer 2. |
| מזהה החותם השלישי | signer-charlie |
MPC Signer 3. |
| מזהה מחבר המדיניות 1 | author-dave |
מחבר המדיניות 1. |
| מזהה מחבר המדיניות 2 | author-eve |
מחבר המדיניות 2. |
| מזהה המדיניות | ethereum-transfer-policy |
מזהה המדיניות (לדוגמה, ethereum-transfer-policy או bitcoin-psbt-policy). |
| מזהה KeyRing | my-key-ring |
מזהה KeyRing. |
| מזהה מפתח | my-key |
מזהה המפתח. |
| מזהה הצעה | my-proposal |
מזהה ההצעה. |
שלב 1: יוצרים מפתחות הסכמה
כדי להשתמש ב-MPC API, צריך ליצור זוגות מפתחות לתפקידים השונים הבאים. שימו לב: אלה מפתחות הסכמה קריפטוגרפיים חיצוניים, והם שונים משברי המפתחות של סף ה-MPC המבוזר שנוצרים באופן פנימי במהלך ההגדרה.
- מחברי מדיניות: מאשרים את כללי מדיניות המשילות.
- חותמים: מספקים הסכמה (הצבעה/אישור) להצעות.
א. מפתחות של המשתתפים (secp256k1)
יוצרים את המפתחות הקריפטוגרפיים הנדרשים למשתתפים. הפקודות האלה הן אידמפוטנטיות.
Cloud KMS
מומלץ לנהל את מפתחות ההסכמה ב-Cloud Key Management Service. מוודאים שלזהות שלכם יש הרשאות ליצור אוספי מפתחות, ליצור מפתחות, לחתום ולראות מאמתים של מפתחות ציבוריים (כמו roles/cloudkms.admin ו-roles/cloudkms.signerVerifier). מוודאים שיצרתם KeyRing, ואז יוצרים מפתח secp256k1 לכל החותמים.
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
אפשר להשתמש ב-OpenSSL רגיל כדי ליצור secp256k1 מפתחות לכל משתתף.
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
ב. מפתחות של מחבר המדיניות (secp256k1)
Cloud KMS
באופן דומה, מומלץ לנהל את מפתחות ההסכמה האלה ב-Cloud Key Management Service. מוודאים שיש לכם הרשאות ליצור אוספי מפתחות, ליצור מפתחות, לחתום ולראות מאמתים של מפתחות ציבוריים (כמו roles/cloudkms.admin ו-roles/cloudkms.signerVerifier). מוודאים שיש לכם KeyRing שנוצר, ואז יוצרים מפתח secp256k1 לכל כותבי המדיניות.
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
כותבי המדיניות משתמשים בעקומת secp256k1 כדי לחתום על מדיניות הפיקוח ועל פעולות הניהול. אפשר ליצור את המפתחות האלה באמצעות OpenSSL רגיל.
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
שלב 2: רישום המשתתפים
לפני שכל ישות יכולה להשתתף בתהליך MPC (כחותם) או בתהליך המדיניות (כמחבר מדיניות), היא חייבת להיות רשומה כמשתתף.
כדי להירשם, נדרש תיאום מחוץ לערוץ בין האדמין לבין המשתתף:
- הישות (החותם או המחבר) יוצרת את זוג המפתחות שלה באופן מקומי ומעבירה את המפתח הציבורי שלה לאדמין באופן מאובטח.
- האדמין (עם הרשאות IAM רגילות של Google Cloud ) שולח קריאה ל-API כדי לרשום את המפתח הציבורי.
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
שלב 3: מגדירים את Policy Engine
מגדירים את Policy Engine על ידי הגדרת חבילת האמון ויצירת כללי מדיניות.
א. הפעלת חבילת האמון של מחבר המדיניות
להגדיר מי הם כותבי המדיניות המורשים. זהו משאב יחיד (singleton) של המכונה.
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
ב. יצירת מדיניות (PENDING_ACTIVE)
מגדירים מזהה מדיניות (ethereum-transfer-policy או bitcoin-psbt-policy) ויוצרים כלל שמתאר אילו פעולות מותרות. אפשר להגדיר הרבה כללי מדיניות לתרחישים שונים, אבל במדריך הזה נראה איך להגדיר מדיניות לעסקאות ב-Ethereum או בביטקוין. כברירת מחדל, Policy Engine מוגדר ל-עצירה מיידית במקרה שגיאה (Block by Default). אם אין מדיניות שמתירה טרנזקציה באופן מפורש, היא נדחית.
לפני שיוצרים או מאשרים מדיניות, המחברים צריכים לחתום על מטען ייעודי (payload) שמייצג את הכוונה. אתם יכולים להשתמש בפונקציות העזר הבאות כדי לטפל בסריאליזציה בינארית ובחתימה.
Ethereum
הכלל הזה מאפשר העברות קטנות יותר של אתריום (<= 2 ETH, שמיוצג כ-2,000,000,000 Gwei) לכתובת ספציפית של אליס.
1. יצירת חתימת קוורום
שימוש ב-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")
שימוש ב-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
# 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.transaction.v2 (BIP 174 PSBT), שבהן סכום ההעברה נטו לא עולה על 250,000 סאטושי (context.transaction.spend_amount_scaled <= 2500000, או 0.0025 BTC מנורמל ל-10-9 יחידות עשרוניות קבועות) ועמלת הכרייה לא עולה על 10,000 סאטושי (context.transaction.max_fee_atomic <= 100000).
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. יצירת חתימת קוורום
שימוש ב-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")
שימוש ב-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
# 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
ג. אישור המדיניות (מעבר לסטטוס ACTIVE)
אם הגדרת המדיניות מחייבת חתימות M-of-N, המחברים הבאים קוראים ל-AddQuorumSignature.
כדי ליצור את החתימה, קודם צריך לאחזר את הרצף הנוכחי של המדיניות.
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
ביטקוין
# 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
שלב 4: יצירת מפתח
עכשיו ניצור אוסף מפתחות ומפתח מבוזר.
א. יצירת אוסף מפתחות (KeyRing)
קיבוץ לוגי של מפתחות ה-MPC.
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
ב. יצירת מפתח מבוזר (DKG)
מתחילים תהליך אסינכרוני ליצירת מפתח מבוזר בין המשתתפים.
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
הפעולה הזו מחזירה פעולה ממושכת (LRO). צריך להמתין עד שהמפתח state יהפוך ל-ACTIVE (כלומר, טקס ה-DKG הצליח).
🕵️♂️ בדיקת מצב המפתח (סקר)
מריצים את הפקודה הבאה מדי פעם כדי לבדוק את סטטוס המפתח, עד שהשדה state בתשובה הופך ל-ACTIVE (מה שמציין שטקס ה-DKG הצליח).
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
ג. הפקת כתובת בלוקצ'יין ואיתור פלטי עסקאות שלא נוצלו (UTXO)
אחרי שהמפתח המבוזר הופך ל-ACTIVE, מפתח ה-MPC מוכן לשימוש. בשלב הזה, אתם מפיקים את הכתובת הספציפית לבלוקצ'יין מהמפתח הציבורי הלא דחוס בגודל 65 בייטים, שמוחזר על ידי יצירת מפתח MPC (0x04 || X || Y), ומאתרים את כל הכספים שלא נוצלו שנדרשים לעסקאות.
Ethereum
כתובות Ethereum נגזרות מגיבוב Keccak-256 של נקודת המפתח הציבורי הלא דחוס בגודל 64 בייטים (pub_bytes[1:]), תוך שימוש ב-20 הבייטים האחרונים.
סקריפט להפקת כתובת (derive_address.py)
מריצים את הבלוק הבא כדי ליצור ולהריץ את derive_address.py:
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
לפני השידור בשלב 6, ודאו שבכתובת הנגזרת של Ethereum יש מספיק Sepolia testnet ETH כדי לכסות את ערך העסקה ואת עלויות הגז. כדי לבקש ETH בחינם ברשת הבדיקה, מזינים את הכתובת החדשה שנוצרה ב-Faucet for Ethereum Sepolia.
ביטקוין
כתובות ביטקוין נגזרות מהמפתח הציבורי הלא דחוס של secp256k1 EC בגודל 65 בייטים (0x04 || X || Y). יש תמיכה בכמה פורמטים של כתובות מאותו מפתח ציבורי:
- Native SegWit (P2WPKH / BIP 173): כתובת Bech32 (
bc1q...ב-Mainnet, tb1q...ב-Testnet4) עםscriptPubKey(0014<hash160>) של 22 בייטים. זה הפורמט המודרני המומלץ עם העמלות הנמוכות ביותר על עסקאות. - Nested SegWit (P2SH-P2WPKH / BIP 49): כתובת SegWit עטופה (
3...ב-Mainnet, 2...ב-Testnet4) עםscriptPubKey(a914<hash160>87) של 23 בייטים ו-redeemScript (0014<hash160>) של 22 בייטים, שתואמת למערכות מדור קודם. - מדור קודם (P2PKH / BIP 44): כתובת Base58 מקורית של Pay-to-PubKey-Hash (
1...ב-Mainnet, m/n...ב-Testnet4) עםscriptPubKey(76a914<hash160>88ac) של 25 בייטים.
סקריפט להפקת כתובת (derive_btc_address.py)
מריצים את הבלוק הבא כדי ליצור ולהריץ את derive_btc_address.py:
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
איתור פלטים שלא נוצלו (UTXO)
בניגוד לבלוקצ'יינים שמבוססים על חשבונות, בעסקאות ביטקוין מוציאים פלטי עסקאות ספציפיים שלא נוצלו (UTXO). לפני שיוצרים עסקה בשלב 5:
- מממנים את כתובת ה-Native SegWit שנגזרה (לדוגמה, באמצעות testnet faucet).
- שולחים שאילתה לגבי פלטי העסקאות שלא נוצלו (UTXO) של הכתובת שלכם באמצעות צומת הביטקוין (
bitcoin-cli listunspent 1 9999999 "[\"tb1q...\"]") או API של סייר בלוקים. - שימו לב ל-4 הפרמטרים הבאים שרלוונטיים לשלב 5:
-
txid: גיבוב (hash) של טרנזקציה בגודל 32 בייטים של הפלט שלא נוצל. -
vout: אינדקס הפלט שמתחיל באפס (לדוגמה,0או1). -
value: הסכום שלא נוצל בסאטושי (לדוגמה,200000סאטושי = 0.002 BTC). -
scriptPubKey: הסקריפט לנעילת הקסדצימלי (לדוגמה,0014...).
-
שלב 5: מבצעים פעולת MPC
מבצעים פעולת MPC על ידי הערכת עסקה ויצירת הצעה.
א. הערכת עסקה (קבלת אסימון הרשאה)
לפני שחותמים על פעולת MPC, צריך לאשר את המטען הייעודי (payload) בהתאם למדיניות כדי לקבל אסימון JWS ואת תקציר החתימה בגודל 32 בייטים.
Ethereum
🛑 מקרה א': דחייה (ערך גבוה מדי)
מנסים לאשר עסקה של 3 ETH (חורג מהמגבלה שלנו של 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
התגובה הצפויה: decision: DENY
✅ מקרה ב': אישור (ערך מותר)
מנסים לאשר עסקה של 1 Gwei (במסגרת המגבלה שלנו של 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'))")
התגובה הצפויה: decision: PERMIT וגם token.
ביטקוין
במודל UTXO של ביטקוין, עסקאות מוציאות פלטים ספציפיים שלא נוצלו. ב-SegWit (BIP 143), כדי לחשב את גיבוב החתימה צריך לדעת את כמות הסאטושי ואת סקריפט הנעילה של הקלט שמוציאים.
מנוע המדיניות דורש פורמט BIP 174 PSBT v0 (BITCOIN_TX_V2) עם PSBT_IN_WITNESS_UTXO מאוכלס לכל קלט.
סקריפט ליצירת PSBT (build_psbt.py)
מריצים את הבלוק הבא כדי להרכיב מטען ייעודי (payload) תקין של BIP 174 PSBT v0:
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"
🛑 מקרה א': דחייה (חריגה ממגבלת העמלה)
ניסיון לאשר עסקה שבה עמלת הכרייה חורגת ממגבלת המדיניות של 10,000 סאטושי. בדוגמה הזו, הגדרת MINER_FEE_SATS = 15000 (וגם CHANGE_SATS = 135000) ב-build_psbt.py משתנה ל-max_fee_atomic = 150000 (חורגת ממגבלת המדיניות של 100000), ויוצרת את המטען הייעודי (payload) הבא שמפעיל החלטת DENY מ-Policy Engine.
# 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
התגובה הצפויה: decision: DENY
✅ מקרה ב': אישור (PSBT מותר של ביטקוין)
ניסיון לאשר את ה-PSBT התקין שנוצר קודם (RECIPIENT_SATS = 50000 ממופה ל-spend_amount_scaled = 500000 <= 2500000 ו-MINER_FEE_SATS = 250 ממופה ל-max_fee_atomic = 2500 <= 100000).
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'))")
התשובה הצפויה: decision: PERMIT יחד עם JWS token ו-sighash של BIP 143 בגודל 32 בייטים digest.
התוצאה:
decision: PERMITאוDENY-
token: אסימון המדיניות של JWS (הוכחת תאימות). -
digest: תקציר החתימה בקידוד הקסדצימלי של 32 בייטים. -
evaluatedPolicyIds: כללי המדיניות שנבדקו והשפיעו על ההחלטה.
ב. יצירת הצעה (התחלת תהליך החתימה)
מציעים את המטען הייעודי (payload) של התקציר לחתימה, ומצרפים את האסימון של Policy Engine שהתקבל בשלב 5א.
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
הפעולה הזו מחזירה פעולה ממושכת (LRO). ההצעה הצפויה state תהיה PENDING_ASSENT (כלומר, היא מוכנה לחתימת המשתתפים).
🕵️♂️ בדיקת סטטוס ההצעה (דגימה)
כדי לבדוק את סטטוס ההצעה, מריצים את הפקודה הבאה מדי פעם.
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
ג. תיעוד ההחלטות של המשתתפים (הסכמה)
המשתתפים צריכים לחתום על ה-hash של ההצעה באמצעות המפתחות הפרטיים המקומיים שלהם (חלקי מפתחות) ולהעביר את האישור.
1. תקציר של הצעת החתימה
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. תיעוד ההחלטה
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
ד. אחזור החתימה הסופית
אחרי שמגיעים לסף ההסכמות, בודקים את מצב ההצעה או מאחזרים את החתימה.
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
אם ההצעה נמצאת במצב SUCCEEDED, צריך לבדוק את השדה finalSignature בתוך ה-JSON שמוחזר.
שלב 6: משדרים את העסקה לבלוקצ'יין
אחרי שההצעה מגיעה למצב SUCCEEDED ואחרי שאחזרתם את finalSignature, השלב האחרון הוא ליצור את העסקה החתומה ולשדר אותה לרשת.
Ethereum
הפקה ושידור של מזהה השחזור
MPC API מחזיר חתימת ECDSA גולמית של 64 בייטים (שכוללת את הערכים r ו-s). השרת מוודא שהחתימה היא בפורמט קנוני low-s (EIP-2). עם זאת, ב-Ethereum נדרש מזהה שחזור (v) כדי להפיק את כתובת השולח ולאמת את החתימה, שלא מוחזרת ישירות בחתימות MPC רגילות של 64 בייט.
סקריפט Python הבא מטפל במורכבות הזו. הוא קובע באופן דינמי את סיבית השחזור הנכונה על ידי התאמה לכתובת השולח הצפויה, בונה את העסקה התקינה ומשדר אותה לרשת.
תסריט לשידור:
מריצים את הבלוק הבא בטרמינל כדי לכתוב ולהריץ את broadcast_eth.py:
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
פתרון שגיאות נפוצות
invalid sender: recovery failedאוinvalid transaction v, r, s values: השגיאה הזו מתרחשת אם הפרמטרים של העסקה בסקריפט לא תואמים במדויק למטען הייעודי (payload) שנחתם. מוודאים שהשדותvalue,nonceו-toתואמים למה שעובד בשלב 5.-
insufficient funds for gas * price + value: העסקה שלכם תקפה מבחינה קריפטוגרפית והתקבלה על ידי הצומת, אבל לכתובת ה-Ethereum של מפתח ה-MPC שלכם אין מספיק ETH ברשת הבדיקה כדי לכסות את הפעולה. צריך להוסיף כסף לכתובת לפני השידור.
ביטקוין
הרכבה ושידור של עסקאות SegWit Wire
בעסקאות Bitcoin Native SegWit (BIP 141), צריך לעצב את חתימת ה-MPC הגולמית של 64 בייטים ((r, s)) ל-canonical low-S ASN.1 DER, להוסיף לה 0x01 (SIGHASH_ALL) ולהרכיב אותה עם המפתח הציבורי הדחוס של 33 בייטים למחסנית העדים של SegWit עם 2 רכיבים: [DER signature || 0x01, compressed public key].
תסריט לשידור:
מריצים את הבלוק הבא כדי ליצור ולהריץ את broadcast_btc.py:
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
פתרון שגיאות נפוצות
-
mandatory-script-verify-flag-failed (Signature must be zero for failed CHECK(MULTI)SIG operation)/non-mandatory-script-verify-flag (Non-canonical DER signature): ודאו שהרכיבsשל החתימה הוא בפורמט low-s קנוני (s <= N/2) ומקודד כראוי ב-DER. -
bad-txns-in-belowout/bad-txns-fee-negative: סך הפלט חורג מערך הקלט. צריך לוודא שחישוב השינוי (InputSatoshis - PaymentSatoshis - MinerFee) הוא חיובי בלבד.
📖 נספח: מדריך תפעולי (שאילתות ובדיקות)
אתם יכולים להשתמש בפקודות האלה לקריאה בלבד כדי לבדוק את מצב המשאבים בכל שלב.
👥 1. משתתפים וחותמים
בודקים את המשתתפים והחותמים במופע שלכם.
א. רשימה של כל המשתתפים הרשומים
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
ב. קבלת פרטים על משתתף ספציפי
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
בודקים את כללי המדיניות ואת חבילות האמון.
א. קבלת חבילת האמון של מחבר המדיניות
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
ב. הצגת רשימה של כל כללי המדיניות
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
ג. קבלת פרטים על מדיניות ספציפית
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. מפתחות ואוספי מפתחות
בדיקת אוספי מפתחות ומפתחות.
א. הצגת רשימה של כל אוספי המפתחות (KeyRings)
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
ב. הצגת רשימה של כל המפתחות ב-KeyRing
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
ג. קבלת הסטטוס של מפתח מבוזר ספציפי (סטטוס DKG)
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. הצעות (פעולות חתימה)
לבדוק את ההצעות ואת סטטוס החתימה.
א. הצגת רשימה של כל ההצעות למפתח
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
ב. קבלת פרטים או סטטוס של הצעה ספציפית
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