From d860f9e273c79771252b06023276b7e3ce162652 Mon Sep 17 00:00:00 2001 From: Richard Liu Date: Thu, 16 Jan 2025 00:07:49 -0800 Subject: [PATCH] pods now consumable with @pcd/pod and @pcd/pod-pcd library --- go/main.go | 143 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 127 insertions(+), 16 deletions(-) diff --git a/go/main.go b/go/main.go index 3daafd2..5e61106 100644 --- a/go/main.go +++ b/go/main.go @@ -17,11 +17,17 @@ extern void free_string(char* ptr); */ import "C" import ( + "encoding/base64" + "encoding/hex" "encoding/json" "fmt" "unsafe" ) +// This matches Rust's `STANDARD_NO_PAD` from the base64 crate. +var noPadB64 = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/").WithPadding(base64.NoPadding) + + // ========== Data Structures for bridging ========== // Minimal shape of PodValue @@ -39,11 +45,7 @@ type KVPair struct { // ========== FFI Wrapper ========== func CreatePod(privateKey []byte, data []KVPair) (string, error) { - // arr := make([][2]interface{}, len(data)) - // for i, kv := range data { - // arr[i][0] = kv.Key - // arr[i][1] = kv.Value - // } + // Build the JSON array-of-arrays that Rust expects: Vec<(String, PodValue)> arr := make([][2]interface{}, len(data)) for i, kv := range data { arr[i][0] = kv.Key @@ -55,8 +57,6 @@ func CreatePod(privateKey []byte, data []KVPair) (string, error) { return "", fmt.Errorf("failed to encode data to JSON: %w", err) } - fmt.Printf("Outgoing JSON: %s\n", string(dataJSON)) - privateKeyPtr := (*C.uchar)(unsafe.Pointer(&privateKey[0])) privateKeyLen := C.ulonglong(len(privateKey)) @@ -71,7 +71,6 @@ func CreatePod(privateKey []byte, data []KVPair) (string, error) { dataJSONPtr, &cErr, ) - defer func() { if resultPtr != nil { C.free_string(resultPtr) @@ -82,11 +81,123 @@ func CreatePod(privateKey []byte, data []KVPair) (string, error) { return "", fmt.Errorf("create_pod_ffi failed, code %d", cErr) } - podJSON := C.GoString(resultPtr) - return podJSON, nil + rustPodJSON := C.GoString(resultPtr) + + // Post-process the JSON into: + // 1. type/value shape for PodValue variants + // 2. Hex-encode signerPublicKey (32 bytes => 64 hex) + // 3. Hex-encode signature (64 bytes => 128 hex) + var raw map[string]interface{} + if err := json.Unmarshal([]byte(rustPodJSON), &raw); err != nil { + return "", fmt.Errorf("failed to unmarshal rustPodJSON: %w", err) + } + + if err := transformEntries(raw); err != nil { + return "", fmt.Errorf("transformEntries: %w", err) + } + if err := hexEncodeSignerPublicKey(raw); err != nil { + return "", fmt.Errorf("hexEncodeSignerPublicKey: %w", err) + } + if err := hexEncodeSignature(raw); err != nil { + return "", fmt.Errorf("hexEncodeSignature: %w", err) + } + + finalBytes, err := json.Marshal(raw) + if err != nil { + return "", fmt.Errorf("failed to marshal final JSON: %w", err) + } + + return string(finalBytes), nil +} + +// Transform the entries from FFI output type/value shape to our type/value shape. +func transformEntries(raw map[string]interface{}) error { + claim, ok := raw["claim"].(map[string]interface{}) + if !ok { + return nil + } + entries, ok := claim["entries"].(map[string]interface{}) + if !ok { + return nil + } + + for key, val := range entries { + if subObj, ok := val.(map[string]interface{}); ok { + converted := convertVariant(subObj) + entries[key] = converted + } + } + return nil +} + +// Convert the entries from FFI output type/value shape to our type/value shape. +func convertVariant(obj map[string]interface{}) map[string]interface{} { + for k, v := range obj { + switch k { + case "String": + return map[string]interface{}{"type": "string", "value": v} + case "Int": + return map[string]interface{}{"type": "int", "value": v} + case "Boolean": + return map[string]interface{}{"type": "boolean", "value": v} + default: + // fallback if we don't recognize the key + return map[string]interface{}{"type": k, "value": v} + } + } + return obj +} + +func hexEncodeSignerPublicKey(raw map[string]interface{}) error { + claim, ok := raw["claim"].(map[string]interface{}) + if !ok { + return nil + } + + spkVal, ok := claim["signerPublicKey"].(string) + if !ok { + return nil + } + + // This string is from Rust's compressed_pt_ser, which uses + // base64::STANDARD_NO_PAD. So let's decode with noPadB64. + decoded, err := noPadB64.DecodeString(spkVal) + if err != nil { + return fmt.Errorf("publicKey not valid no-pad base64: %v", err) + } + if len(decoded) != 32 { + return fmt.Errorf("publicKey is %d bytes, expected 32", len(decoded)) + } + + hexVal := hex.EncodeToString(decoded) + claim["signerPublicKey"] = hexVal + return nil +} + +func hexEncodeSignature(raw map[string]interface{}) error { + proof, ok := raw["proof"].(map[string]interface{}) + if !ok { + return nil + } + + sigVal, ok := proof["signature"].(string) + if !ok { + return nil + } + + decoded, err := noPadB64.DecodeString(sigVal) + if err != nil { + return fmt.Errorf("signature not valid no-pad base64: %v", err) + } + if len(decoded) != 64 { + return fmt.Errorf("signature is %d bytes, expected 64", len(decoded)) + } + + hexVal := hex.EncodeToString(decoded) + proof["signature"] = hexVal + return nil } -// ========== MAIN PROGRAM ========== func main() { // Example private key @@ -97,15 +208,15 @@ func main() { data := []KVPair{ { - Key: "hello", + Key: "created_by", Value: PodValue{ - String: ptrString("world"), + String: ptrString("Golang"), }, }, { - Key: "count", + Key: "year", Value: PodValue{ - Int: ptrInt64(42), + Int: ptrInt64(2025), }, }, } @@ -115,7 +226,7 @@ func main() { return } - fmt.Println("Created POD JSON:", result) + fmt.Println(result) } func ptrString(s string) *string { return &s }