Skip to content

Commit

Permalink
pods now consumable with @pcd/pod and @pcd/pod-pcd library
Browse files Browse the repository at this point in the history
  • Loading branch information
rrrliu committed Jan 16, 2025
1 parent f235031 commit d860f9e
Showing 1 changed file with 127 additions and 16 deletions.
143 changes: 127 additions & 16 deletions go/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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))

Expand All @@ -71,7 +71,6 @@ func CreatePod(privateKey []byte, data []KVPair) (string, error) {
dataJSONPtr,
&cErr,
)

defer func() {
if resultPtr != nil {
C.free_string(resultPtr)
Expand All @@ -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
Expand All @@ -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),
},
},
}
Expand All @@ -115,7 +226,7 @@ func main() {
return
}

fmt.Println("Created POD JSON:", result)
fmt.Println(result)
}

func ptrString(s string) *string { return &s }
Expand Down

0 comments on commit d860f9e

Please sign in to comment.