diff --git a/bjj.go b/bjj.go new file mode 100644 index 0000000..cc9dfda --- /dev/null +++ b/bjj.go @@ -0,0 +1,208 @@ +package c_polygonid + +import ( + "context" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + + "github.com/iden3/go-iden3-crypto/babyjub" +) + +type BabyJubJubSignPoseidonResponse struct { + Signature babyjub.SignatureComp `json:"signature"` +} + +func BabyJubJubSignPoseidon(ctx context.Context, cfg EnvConfig, + in []byte) (BabyJubJubSignPoseidonResponse, error) { + + var req struct { + PrivKey *JsonBJJPrivateKey `json:"private_key"` + Msg *JsonFieldIntStr `json:"msg_int"` + } + + if in == nil { + return BabyJubJubSignPoseidonResponse{}, errors.New("request is empty") + } + + err := json.Unmarshal(in, &req) + if err != nil { + return BabyJubJubSignPoseidonResponse{}, + fmt.Errorf("failed to unmarshal request: %w", err) + } + + if req.PrivKey == nil { + return BabyJubJubSignPoseidonResponse{}, + errors.New("private key is not set") + } + if req.Msg == nil { + return BabyJubJubSignPoseidonResponse{}, + errors.New("message is not set") + } + + sig := req.PrivKey.PrivateKey().SignPoseidon(req.Msg.Int()) + return BabyJubJubSignPoseidonResponse{ + Signature: sig.Compress(), + }, nil +} + +type BabyJubJubVerifyPoseidonResponse struct { + Valid bool `json:"valid"` +} + +func BabyJubJubVerifyPoseidon(_ context.Context, _ EnvConfig, + in []byte) (BabyJubJubVerifyPoseidonResponse, error) { + + var req struct { + Pub *JsonBJJPublicKey `json:"public_key"` + Sig *JsonBJJSignature `json:"signature"` + Msg *JsonFieldIntStr `json:"msg_int"` + } + + if in == nil { + return BabyJubJubVerifyPoseidonResponse{}, + errors.New("request is empty") + } + + err := json.Unmarshal(in, &req) + if err != nil { + return BabyJubJubVerifyPoseidonResponse{}, + fmt.Errorf("failed to unmarshal request: %w", err) + } + + if req.Pub == nil { + return BabyJubJubVerifyPoseidonResponse{}, + errors.New("public key is not set") + } + if req.Msg == nil { + return BabyJubJubVerifyPoseidonResponse{}, + errors.New("message is not set") + } + if req.Sig == nil { + return BabyJubJubVerifyPoseidonResponse{}, + errors.New("signature is not set") + } + + return BabyJubJubVerifyPoseidonResponse{ + Valid: req.Pub.PublicKey(). + VerifyPoseidon(req.Msg.Int(), req.Sig.Signature()), + }, nil +} + +type BabyJubJubPrivate2PublicResponse struct { + PublicKey string `json:"public_key"` + PublicKeyX string `json:"public_key_x_int"` + PublicKeyY string `json:"public_key_y_int"` +} + +func BabyJubJubPrivate2Public(_ context.Context, _ EnvConfig, + in []byte) (BabyJubJubPrivate2PublicResponse, error) { + + var req struct { + PrivKey *JsonBJJPrivateKey `json:"private_key"` + } + + if in == nil { + return BabyJubJubPrivate2PublicResponse{}, + errors.New("request is empty") + } + + err := json.Unmarshal(in, &req) + if err != nil { + return BabyJubJubPrivate2PublicResponse{}, + fmt.Errorf("failed to unmarshal request: %w", err) + } + + if req.PrivKey == nil { + return BabyJubJubPrivate2PublicResponse{}, + errors.New("private key is not set") + } + + pubKey := req.PrivKey.PrivateKey().Public() + compPubKey := pubKey.Compress() + + return BabyJubJubPrivate2PublicResponse{ + PublicKey: hex.EncodeToString(compPubKey[:]), + PublicKeyX: pubKey.X.Text(10), + PublicKeyY: pubKey.Y.Text(10), + }, nil +} + +type BabyJubJubPublicUncompressResponse struct { + PublicKeyX string `json:"public_key_x_int"` + PublicKeyY string `json:"public_key_y_int"` +} + +func BabyJubJubPublicUncompress(_ context.Context, _ EnvConfig, + in []byte) (BabyJubJubPublicUncompressResponse, error) { + + var req struct { + Pub *JsonBJJPublicKey `json:"public_key"` + } + + if in == nil { + return BabyJubJubPublicUncompressResponse{}, + errors.New("request is empty") + } + + err := json.Unmarshal(in, &req) + if err != nil { + return BabyJubJubPublicUncompressResponse{}, + fmt.Errorf("failed to unmarshal request: %w", err) + } + + if req.Pub == nil { + return BabyJubJubPublicUncompressResponse{}, + errors.New("public key is not set") + } + + return BabyJubJubPublicUncompressResponse{ + PublicKeyX: req.Pub.PublicKey().X.Text(10), + PublicKeyY: req.Pub.PublicKey().Y.Text(10), + }, nil +} + +type BabyJubJubPublicCompressResponse struct { + PublicKey string `json:"public_key"` +} + +func BabyJubJubPublicCompress(_ context.Context, _ EnvConfig, + in []byte) (BabyJubJubPublicCompressResponse, error) { + + var req struct { + PublicKeyX *JsonFieldIntStr `json:"public_key_x_int"` + PublicKeyY *JsonFieldIntStr `json:"public_key_y_int"` + } + + if in == nil { + return BabyJubJubPublicCompressResponse{}, + errors.New("request is empty") + } + + err := json.Unmarshal(in, &req) + if err != nil { + return BabyJubJubPublicCompressResponse{}, + fmt.Errorf("failed to unmarshal request: %w", err) + } + + if req.PublicKeyX == nil { + return BabyJubJubPublicCompressResponse{}, + errors.New("public key X is not set") + } + + if req.PublicKeyY == nil { + return BabyJubJubPublicCompressResponse{}, + errors.New("public key Y is not set") + } + + pubKey := babyjub.PublicKey{ + X: req.PublicKeyX.Int(), + Y: req.PublicKeyY.Int(), + } + compPubKey := pubKey.Compress() + + return BabyJubJubPublicCompressResponse{ + PublicKey: hex.EncodeToString(compPubKey[:]), + }, nil +} diff --git a/bjj_test.go b/bjj_test.go new file mode 100644 index 0000000..eeebc60 --- /dev/null +++ b/bjj_test.go @@ -0,0 +1,232 @@ +package c_polygonid + +import ( + "context" + "encoding/json" + "strconv" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestBabyJubJubSignPoseidon(t *testing.T) { + testCases := []struct { + in string + want string + wantErr string + }{ + { + in: `{ + "private_key": "b8284dcade2f26c5ddd3b6ac5c1d728ecae9b23250bb5d35d40a3dd33f048e" +}`, + wantErr: `failed to unmarshal request: invalid private key length`, + }, + { + in: `{ + "private_key": "b8284dcade2f26c5ddd3b6ac524c1d728ecae9b23250bb5d35d40a3dd33f048e" +}`, + wantErr: `message is not set`, + }, + { + in: `{ + "private_key": "b8284dcade2f26c5ddd3b6ac524c1d728ecae9b23250bb5d35d40a3dd33f048e", + "msg_int": "100500" +}`, + want: `{ + "signature": "c69e5ca2806d17158b35f29ff8f92b8cc00930d124261c4180573ce07ec80f0ad2f68a3073d56a0fab81a581a1b5f4925a504a019db3779a0fc398ee37059b05" +}`, + }, + { + in: `{}`, + wantErr: `private key is not set`, + }, + } + + for idx, tc := range testCases { + t.Run(strconv.Itoa(idx), func(t *testing.T) { + ctx := context.Background() + resp, err := BabyJubJubSignPoseidon(ctx, EnvConfig{}, []byte(tc.in)) + if tc.wantErr != "" { + require.Error(t, err) + require.Equal(t, tc.wantErr, err.Error()) + } else { + require.NoError(t, err) + respJson, err := json.Marshal(resp) + require.NoError(t, err) + require.JSONEq(t, tc.want, string(respJson)) + } + }) + } +} + +func TestBabyJubJubVerifyPoseidon(t *testing.T) { + testCases := []struct { + in string + want string + wantErr string + }{ + { + in: `{ + "public_key": "b6d7e677e96eae743551a4db2f9d78e7e974a65e5965dce933c17e1bc81380a2", + "msg_int": "100500" +}`, + wantErr: `signature is not set`, + }, + { + in: `{ + "public_key": "b6d7e677e96eae743551a4db2f9d78e7e974a65e5965dce933c17e1bc81380a2", + "msg_int": "100500", + "signature": "c69e5ca2806d17158b35f29ff8f92b8cc00930d124261c4180573ce07ec80f0ad2f68a3073d56a0fab81a581a1b5f4925a504a019db3779a0fc398ee37059b05" +}`, + want: `{"valid": true}`, + }, + { + in: `{ + "public_key": "b6d7e677e96eae743551a4db2f9d78e7e974a65e5965dce933c17e1bc81380a2", + "msg_int": "100501", + "signature": "c69e5ca2806d17158b35f29ff8f92b8cc00930d124261c4180573ce07ec80f0ad2f68a3073d56a0fab81a581a1b5f4925a504a019db3779a0fc398ee37059b05" +}`, + want: `{"valid": false}`, + }, + } + + for idx, tc := range testCases { + t.Run(strconv.Itoa(idx), func(t *testing.T) { + ctx := context.Background() + resp, err := BabyJubJubVerifyPoseidon(ctx, EnvConfig{}, []byte(tc.in)) + if tc.wantErr != "" { + require.Error(t, err) + require.Equal(t, tc.wantErr, err.Error()) + } else { + require.NoError(t, err) + respJson, err := json.Marshal(resp) + require.NoError(t, err) + require.JSONEq(t, tc.want, string(respJson)) + } + }) + } +} + +func TestBabyJubJubPrivate2Public(t *testing.T) { + testCases := []struct { + in string + want string + wantErr string + }{ + { + in: `{ + "private_key": "b8284dcade2f26c5ddd3b6ac524c1d728ecae9b23250bb5d35d40a3dd33f048e" +}`, + want: `{ + "public_key": "b6d7e677e96eae743551a4db2f9d78e7e974a65e5965dce933c17e1bc81380a2", + "public_key_x_int": "21241797485017627462131959691037462653200294188088281099570857109629667091940", + "public_key_y_int": "15604929804188188583235249350576701257034402006903904828344484717210494228406" +}`, + }, + { + in: `{ + "private_key2": "b8284dcade2f26c5ddd3b6ac524c1d728ecae9b23250bb5d35d40a3dd33f048e" +}`, + wantErr: `private key is not set`, + }, + } + + for idx, tc := range testCases { + t.Run(strconv.Itoa(idx), func(t *testing.T) { + ctx := context.Background() + resp, err := BabyJubJubPrivate2Public(ctx, EnvConfig{}, + []byte(tc.in)) + if tc.wantErr != "" { + require.Error(t, err) + require.Equal(t, tc.wantErr, err.Error()) + } else { + require.NoError(t, err) + respJson, err := json.Marshal(resp) + require.NoError(t, err) + require.JSONEq(t, tc.want, string(respJson)) + } + }) + } +} + +func TestBabyJubJubPublicUncompress(t *testing.T) { + testCases := []struct { + in string + want string + wantErr string + }{ + { + in: `{ + "public_key": "b6d7e677e96eae743551a4db2f9d78e7e974a65e5965dce933c17e1bc81380a2" +}`, + want: `{ + "public_key_x_int": "21241797485017627462131959691037462653200294188088281099570857109629667091940", + "public_key_y_int": "15604929804188188583235249350576701257034402006903904828344484717210494228406" +}`, + }, + { + in: `{ + "private_key2": "b8284dcade2f26c5ddd3b6ac524c1d728ecae9b23250bb5d35d40a3dd33f048e" +}`, + wantErr: `public key is not set`, + }, + } + + for idx, tc := range testCases { + t.Run(strconv.Itoa(idx), func(t *testing.T) { + ctx := context.Background() + resp, err := BabyJubJubPublicUncompress(ctx, EnvConfig{}, + []byte(tc.in)) + if tc.wantErr != "" { + require.Error(t, err) + require.Equal(t, tc.wantErr, err.Error()) + } else { + require.NoError(t, err) + respJson, err := json.Marshal(resp) + require.NoError(t, err) + require.JSONEq(t, tc.want, string(respJson)) + } + }) + } +} + +func TestBabyJubJubPublicCompress(t *testing.T) { + testCases := []struct { + in string + want string + wantErr string + }{ + { + in: `{ + "public_key_x_int": "21241797485017627462131959691037462653200294188088281099570857109629667091940", + "public_key_y_int": "15604929804188188583235249350576701257034402006903904828344484717210494228406" +}`, + want: `{ + "public_key": "b6d7e677e96eae743551a4db2f9d78e7e974a65e5965dce933c17e1bc81380a2" +}`, + }, + { + in: `{ + "private_key2": "b8284dcade2f26c5ddd3b6ac524c1d728ecae9b23250bb5d35d40a3dd33f048e" +}`, + wantErr: `public key X is not set`, + }, + } + + for idx, tc := range testCases { + t.Run(strconv.Itoa(idx), func(t *testing.T) { + ctx := context.Background() + resp, err := BabyJubJubPublicCompress(ctx, EnvConfig{}, + []byte(tc.in)) + if tc.wantErr != "" { + require.Error(t, err) + require.Equal(t, tc.wantErr, err.Error()) + } else { + require.NoError(t, err) + respJson, err := json.Marshal(resp) + require.NoError(t, err) + require.JSONEq(t, tc.want, string(respJson)) + } + }) + } +} diff --git a/cmd/polygonid/polygonid.go b/cmd/polygonid/polygonid.go index f6070b7..c915c76 100644 --- a/cmd/polygonid/polygonid.go +++ b/cmd/polygonid/polygonid.go @@ -948,6 +948,46 @@ func PLGNDescribeID(jsonResponse **C.char, in *C.char, cfg *C.char, return true } +//export PLGNBabyJubJubSignPoseidon +func PLGNBabyJubJubSignPoseidon(jsonResponse **C.char, in *C.char, cfg *C.char, + status **C.PLGNStatus) bool { + + return callGenericFn(c_polygonid.BabyJubJubSignPoseidon, jsonResponse, in, + cfg, status) +} + +//export PLGNBabyJubJubVerifyPoseidon +func PLGNBabyJubJubVerifyPoseidon(jsonResponse **C.char, in *C.char, + cfg *C.char, status **C.PLGNStatus) bool { + + return callGenericFn(c_polygonid.BabyJubJubVerifyPoseidon, jsonResponse, in, + cfg, status) +} + +//export PLGNBabyJubJubPrivate2Public +func PLGNBabyJubJubPrivate2Public(jsonResponse **C.char, in *C.char, + cfg *C.char, status **C.PLGNStatus) bool { + + return callGenericFn(c_polygonid.BabyJubJubPrivate2Public, jsonResponse, in, + cfg, status) +} + +//export PLGNBabyJubJubPublicUncompress +func PLGNBabyJubJubPublicUncompress(jsonResponse **C.char, in *C.char, + cfg *C.char, status **C.PLGNStatus) bool { + + return callGenericFn(c_polygonid.BabyJubJubPublicUncompress, jsonResponse, + in, cfg, status) +} + +//export PLGNBabyJubJubPublicCompress +func PLGNBabyJubJubPublicCompress(jsonResponse **C.char, in *C.char, + cfg *C.char, status **C.PLGNStatus) bool { + + return callGenericFn(c_polygonid.BabyJubJubPublicCompress, jsonResponse, + in, cfg, status) +} + // createEnvConfig returns empty config if input json is nil. func createEnvConfig(cfgJson *C.char) (c_polygonid.EnvConfig, error) { var cfgData []byte diff --git a/types.go b/types.go index f349c89..7a7bacc 100644 --- a/types.go +++ b/types.go @@ -192,3 +192,113 @@ func (d *coreDID) UnmarshalJSON(bytes []byte) error { *d = coreDID(*did) return nil } + +type JsonBJJPrivateKey babyjub.PrivateKey + +func (i *JsonBJJPrivateKey) PrivateKey() *babyjub.PrivateKey { + return (*babyjub.PrivateKey)(i) +} + +func (i *JsonBJJPrivateKey) UnmarshalJSON(bytes []byte) error { + var s *string + err := json.Unmarshal(bytes, &s) + if err != nil { + return err + } + + if s == nil { + return fmt.Errorf("private key is not set") + } + + if len(*s) != len(*((*babyjub.PrivateKey)(i)))*2 { + return fmt.Errorf("invalid private key length") + } + + n, err := hex.Decode((*i)[:], []byte(*s)) + if err != nil { + return err + } + if n != len(*((*babyjub.PrivateKey)(i))) { + return fmt.Errorf("can't fully decode private key") + } + + return nil +} + +type JsonBJJPublicKey babyjub.PublicKey + +func (i *JsonBJJPublicKey) PublicKey() *babyjub.PublicKey { + return (*babyjub.PublicKey)(i) +} + +func (i *JsonBJJPublicKey) UnmarshalJSON(bytes []byte) error { + var s *string + err := json.Unmarshal(bytes, &s) + if err != nil { + return err + } + + if s == nil { + return fmt.Errorf("public key is not set") + } + + var pk babyjub.PublicKeyComp + if len(*s) != len(pk)*2 { + return fmt.Errorf("invalid public key length") + } + + n, err := hex.Decode(pk[:], []byte(*s)) + if err != nil { + return err + } + if n != len(pk) { + return fmt.Errorf("can't fully decode public key") + } + + pkp, err := pk.Decompress() + if err != nil { + return err + } + + *(*babyjub.PublicKey)(i) = *pkp + return nil +} + +type JsonBJJSignature babyjub.Signature + +func (i *JsonBJJSignature) Signature() *babyjub.Signature { + return (*babyjub.Signature)(i) +} + +func (i *JsonBJJSignature) UnmarshalJSON(bytes []byte) error { + var s *string + err := json.Unmarshal(bytes, &s) + if err != nil { + return err + } + + if s == nil { + return fmt.Errorf("signature is not set") + } + + var sigComp babyjub.SignatureComp + if len(*s) != len(sigComp)*2 { + return fmt.Errorf("invalid signature length") + } + + n, err := hex.Decode(sigComp[:], []byte(*s)) + if err != nil { + return err + } + if n != len(sigComp) { + return fmt.Errorf("can't fully decode signature") + } + + sig, err := sigComp.Decompress() + if err != nil { + return err + } + + *(*babyjub.Signature)(i) = *sig + return nil +} diff --git a/types_test.go b/types_test.go index bee35e3..6e31467 100644 --- a/types_test.go +++ b/types_test.go @@ -1,10 +1,13 @@ package c_polygonid import ( + "encoding/hex" "encoding/json" "math/big" + "strconv" "testing" + "github.com/iden3/go-iden3-crypto/babyjub" "github.com/stretchr/testify/require" ) @@ -85,3 +88,54 @@ func TestJsonByte_UnmarshalJSON(t *testing.T) { }) } } + +func TestJsonBJJPrivateKey_UnmarshalJSON(t *testing.T) { + type tp struct { + Jp *JsonBJJPrivateKey `json:"key"` + } + + keyFromHex := func(s string) *babyjub.PrivateKey { + b, err := hex.DecodeString(s) + require.NoError(t, err) + var k babyjub.PrivateKey + copy(k[:], b) + return &k + } + + testCases := []struct { + in string + wantErr string + want *babyjub.PrivateKey + }{ + { + in: `{"key":"0x123"}`, + wantErr: `invalid private key length`, + }, + { + in: `{"key":"e3addb905b705a89d849adef89227eb6664a9785823df48451300754c91f42cd"}`, + want: keyFromHex(`e3addb905b705a89d849adef89227eb6664a9785823df48451300754c91f42cd`), + }, + { + in: `{"key":"e3addb905b705a89d849adef89227eb6664a9785823df48451300754c91f42cx"}`, + wantErr: "encoding/hex: invalid byte: U+0078 'x'", + }, + { + in: `{}`, + want: nil, + }, + } + for idx, tc := range testCases { + t.Run(strconv.Itoa(idx), func(t *testing.T) { + var j tp + err := json.Unmarshal([]byte(tc.in), &j) + if tc.wantErr != "" { + require.EqualError(t, err, tc.wantErr) + return + } else { + require.NoError(t, err) + require.Equal(t, tc.want, (*babyjub.PrivateKey)(j.Jp)) + } + require.NoError(t, err) + }) + } +}