feature/1114 implement account interface (#1167)

- **refactor: move session-related code to middleware package**
- **refactor: update PKL build process and adjust related
configurations**
- **feat: integrate base.cosmos.v1 Genesis module**
- **refactor: pass session context to modal rendering functions**
- **refactor: move nebula package to app directory and update templ
version**
- **refactor: Move home section video view to dedicated directory**
- **refactor: remove unused views file**
- **refactor: move styles and UI components to global scope**
- **refactor: Rename images.go to cdn.go**
- **feat: Add Empty State Illustrations**
- **refactor: Consolidate Vault Index Logic**
- **fix: References to App.wasm and remove Vault Directory embedded CDN
files**
- **refactor: Move CDN types to Models**
- **fix: Correct line numbers in templ error messages for
arch_templ.go**
- **refactor: use common types for peer roles**
- **refactor: move common types and ORM to a shared package**
- **fix: Config import dwn**
- **refactor: move nebula directory to app**
- **feat: Rebuild nebula**
- **fix: correct file paths in panels templates**
- **feat: Remove duplicate types**
- **refactor: Move dwn to pkg/core**
- **refactor: Binary Structure**
- **feat: Introduce Crypto Pkg**
- **fix: Broken Process Start**
- **feat: Update pkg/* structure**
- **feat: Refactor PKL Structure**
- **build: update pkl build process**
- **chore: Remove Empty Files**
- **refactor: remove unused macaroon package**
- **feat: Add WebAwesome Components**
- **refactor: consolidate build and generation tasks into a single
taskfile, remove redundant makefile targets**
- **refactor: refactor server and move components to pkg/core/dwn**
- **build: update go modules**
- **refactor: move gateway logic into dedicated hway command**
- **feat: Add KSS (Krawczyk-Song-Song) MPC cryptography module**
- **feat: Implement MPC-based JWT signing and UCAN token generation**
- **feat: add support for MPC-based JWT signing**
- **feat: Implement MPC-based UCAN capabilities for smart accounts**
- **feat: add address field to keyshareSource**
- **feat: Add comprehensive MPC test suite for keyshares, UCAN tokens,
and token attenuations**
- **refactor: improve MPC keyshare management and signing process**
- **feat: enhance MPC capability hierarchy documentation**
- **refactor: rename GenerateKeyshares function to NewKeyshareSource for
clarity**
- **refactor: remove unused Ethereum address computation**
- **feat: Add HasHandle and IsAuthenticated methods to HTTPContext**
- **refactor: Add context.Context support to session HTTPContext**
- **refactor: Resolve context interface conflicts in HTTPContext**
- **feat: Add session ID context key and helper functions**
- **feat: Update WebApp Page Rendering**
- **refactor: Simplify context management by using single HTTPContext
key**
- **refactor: Simplify HTTPContext creation and context management in
session middleware**
- **refactor: refactor session middleware to use a single data
structure**
- **refactor: Simplify HTTPContext implementation and session data
handling**
- **refactor: Improve session context handling and prevent nil pointer
errors**
- **refactor: Improve session context handling with nil safety and type
support**
- **refactor: improve session data injection**
- **feat: add full-screen modal component and update registration flow**
- **chore: add .air.toml to .gitignore**
- **feat: add Air to devbox and update dependencies**
This commit is contained in:
Prad Nukala
2024-11-23 01:28:58 -05:00
committed by GitHub
parent bf94277b0f
commit 89989fa102
549 changed files with 74162 additions and 9856 deletions
-1
View File
@@ -1 +0,0 @@
# Common
-68
View File
@@ -1,68 +0,0 @@
package ctx
import (
"github.com/go-webauthn/webauthn/protocol"
"github.com/labstack/echo/v4"
"github.com/segmentio/ksuid"
)
// CookieKey is a type alias for string.
type CookieKey string
const (
// CookieKeySessionID is the key for the session ID cookie.
CookieKeySessionID CookieKey = "session.id"
// CookieKeySessionChal is the key for the session challenge cookie.
CookieKeySessionChal CookieKey = "session.chal"
// CookieKeySonrAddr is the key for the Sonr address cookie.
CookieKeySonrAddr CookieKey = "sonr.addr"
// CookieKeySonrDID is the key for the Sonr DID cookie.
CookieKeySonrDID CookieKey = "sonr.did"
// CookieKeyVaultCID is the key for the Vault CID cookie.
CookieKeyVaultCID CookieKey = "vault.cid"
// CookieKeyVaultSchema is the key for the Vault schema cookie.
CookieKeyVaultSchema CookieKey = "vault.schema"
)
// String returns the string representation of the CookieKey.
func (c CookieKey) String() string {
return string(c)
}
// GetSessionID returns the session ID from the cookies.
func GetSessionID(c echo.Context) string {
// Attempt to read the session ID from the "session" cookie
sessionID, err := ReadCookie(c, CookieKeySessionID)
if err != nil {
// Generate a new KSUID if the session cookie is missing or invalid
WriteCookie(c, CookieKeySessionID, ksuid.New().String())
}
return sessionID
}
// GetSessionChallenge returns the session challenge from the cookies.
func GetSessionChallenge(c echo.Context) (*protocol.URLEncodedBase64, error) {
// TODO: Implement a way to regenerate the challenge if it is invalid.
chal := new(protocol.URLEncodedBase64)
// Attempt to read the session challenge from the "session" cookie
sessionChal, err := ReadCookie(c, CookieKeySessionChal)
if err != nil {
// Generate a new challenge if the session cookie is missing or invalid
ch, errb := protocol.CreateChallenge()
if errb != nil {
return nil, err
}
WriteCookie(c, CookieKeySessionChal, ch.String())
return &ch, nil
}
err = chal.UnmarshalJSON([]byte(sessionChal))
if err != nil {
return nil, err
}
return chal, nil
}
-90
View File
@@ -1,90 +0,0 @@
package ctx
import (
"encoding/json"
"net/http"
"github.com/labstack/echo/v4"
"github.com/onsonr/sonr/pkg/motr/config"
)
// ╭───────────────────────────────────────────────────────────╮
// │ DWNContext struct methods │
// ╰───────────────────────────────────────────────────────────╯
// DWNContext is the context for DWN endpoints.
type DWNContext struct {
echo.Context
// Defaults
id string // Generated ksuid http cookie; Initialized on first request
}
// HasAuthorization returns true if the request has an Authorization header.
func (s *DWNContext) HasAuthorization() bool {
v := ReadHeader(s.Context, HeaderAuthorization)
return v != ""
}
// ID returns the ksuid http cookie.
func (s *DWNContext) ID() string {
return s.id
}
// Address returns the sonr address from the cookies.
func (s *DWNContext) Address() string {
v, err := ReadCookie(s.Context, CookieKeySonrAddr)
if err != nil {
return ""
}
return v
}
// IPFSGatewayURL returns the IPFS gateway URL from the headers.
func (s *DWNContext) IPFSGatewayURL() string {
return ReadHeader(s.Context, HeaderIPFSGatewayURL)
}
// ChainID returns the chain ID from the headers.
func (s *DWNContext) ChainID() string {
return ReadHeader(s.Context, HeaderSonrChainID)
}
// Schema returns the vault schema from the cookies.
func (s *DWNContext) Schema() *config.Schema {
v, err := ReadCookie(s.Context, CookieKeyVaultSchema)
if err != nil {
return nil
}
var schema config.Schema
err = json.Unmarshal([]byte(v), &schema)
if err != nil {
return nil
}
return &schema
}
// GetDWNContext returns the DWNContext from the echo context.
func GetDWNContext(c echo.Context) (*DWNContext, error) {
ctx, ok := c.(*DWNContext)
if !ok {
return nil, echo.NewHTTPError(http.StatusInternalServerError, "DWN Context not found")
}
return ctx, nil
}
// HighwaySessionMiddleware establishes a Session Cookie.
func DWNSessionMiddleware(config *config.Config) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
sessionID := GetSessionID(c)
injectConfig(c, config)
cc := &DWNContext{
Context: c,
id: sessionID,
}
return next(cc)
}
}
}
-45
View File
@@ -1,45 +0,0 @@
package ctx
import (
"net/http"
"github.com/labstack/echo/v4"
)
// ╭───────────────────────────────────────────────────────────╮
// │ HwayContext struct methods │
// ╰───────────────────────────────────────────────────────────╯
// HwayContext is the context for Highway endpoints.
type HwayContext struct {
echo.Context
// Defaults
id string // Generated ksuid http cookie; Initialized on first request
}
// ID returns the ksuid http cookie
func (s *HwayContext) ID() string {
return s.id
}
// GetHwayContext returns the HwayContext from the echo context.
func GetHWAYContext(c echo.Context) (*HwayContext, error) {
ctx, ok := c.(*HwayContext)
if !ok {
return nil, echo.NewHTTPError(http.StatusInternalServerError, "Highway Context not found")
}
return ctx, nil
}
// HighwaySessionMiddleware establishes a Session Cookie.
func HighwaySessionMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
sessionID := GetSessionID(c)
cc := &HwayContext{
Context: c,
id: sessionID,
}
return next(cc)
}
}
-38
View File
@@ -1,38 +0,0 @@
package ctx
import (
"encoding/json"
"github.com/labstack/echo/v4"
dwngen "github.com/onsonr/sonr/pkg/motr/config"
)
type HeaderKey string
const (
HeaderAuthorization HeaderKey = "Authorization"
HeaderIPFSGatewayURL HeaderKey = "X-IPFS-Gateway"
HeaderSonrChainID HeaderKey = "X-Sonr-ChainID"
HeaderSonrKeyshare HeaderKey = "X-Sonr-Keyshare"
)
func (h HeaderKey) String() string {
return string(h)
}
func injectConfig(c echo.Context, config *dwngen.Config) {
WriteHeader(c, HeaderIPFSGatewayURL, config.IpfsGatewayUrl)
WriteHeader(c, HeaderSonrChainID, config.SonrChainId)
WriteHeader(c, HeaderSonrKeyshare, config.MotrKeyshare)
WriteCookie(c, CookieKeySonrAddr, config.MotrAddress)
schemaBz, err := json.Marshal(config.VaultSchema)
if err != nil {
c.Logger().Error(err)
return
}
WriteCookie(c, CookieKeyVaultSchema, string(schemaBz))
}
-35
View File
@@ -1,35 +0,0 @@
package ctx
// ╭───────────────────────────────────────────────────────────╮
// │ Request Headers │
// ╰───────────────────────────────────────────────────────────╯
type RequestHeaders struct {
CacheControl *string `header:"Cache-Control"`
DeviceMemory *string `header:"Device-Memory"`
From *string `header:"From"`
Host *string `header:"Host"`
Referer *string `header:"Referer"`
UserAgent *string `header:"User-Agent"`
ViewportWidth *string `header:"Viewport-Width"`
Width *string `header:"Width"`
// HTMX Specific
HXBoosted *string `header:"HX-Boosted"`
HXCurrentURL *string `header:"HX-Current-URL"`
HXHistoryRestoreRequest *string `header:"HX-History-Restore-Request"`
HXPrompt *string `header:"HX-Prompt"`
HXRequest *string `header:"HX-Request"`
HXTarget *string `header:"HX-Target"`
HXTriggerName *string `header:"HX-Trigger-Name"`
HXTrigger *string `header:"HX-Trigger"`
}
type ProtectedRequestHeaders struct {
Authorization *string `header:"Authorization"`
Forwarded *string `header:"Forwarded"`
Link *string `header:"Link"`
PermissionsPolicy *string `header:"Permissions-Policy"`
ProxyAuthorization *string `header:"Proxy-Authorization"`
WWWAuthenticate *string `header:"WWW-Authenticate"`
}
-38
View File
@@ -1,38 +0,0 @@
package ctx
import "github.com/go-webauthn/webauthn/protocol"
type WebBytes = protocol.URLEncodedBase64
// ╭───────────────────────────────────────────────────────────╮
// │ Response Headers │
// ╰───────────────────────────────────────────────────────────╯
type ResponseHeaders struct {
// HTMX Specific
HXLocation *string `header:"HX-Location"`
HXPushURL *string `header:"HX-Push-Url"`
HXRedirect *string `header:"HX-Redirect"`
HXRefresh *string `header:"HX-Refresh"`
HXReplaceURL *string `header:"HX-Replace-Url"`
HXReswap *string `header:"HX-Reswap"`
HXRetarget *string `header:"HX-Retarget"`
HXReselect *string `header:"HX-Reselect"`
HXTrigger *string `header:"HX-Trigger"`
HXTriggerAfterSettle *string `header:"HX-Trigger-After-Settle"`
HXTriggerAfterSwap *string `header:"HX-Trigger-After-Swap"`
}
type ProtectedResponseHeaders struct {
AcceptCH *string `header:"Accept-CH"`
AccessControlAllowCredentials *string `header:"Access-Control-Allow-Credentials"`
AccessControlAllowHeaders *string `header:"Access-Control-Allow-Headers"`
AccessControlAllowMethods *string `header:"Access-Control-Allow-Methods"`
AccessControlExposeHeaders *string `header:"Access-Control-Expose-Headers"`
AccessControlRequestHeaders *string `header:"Access-Control-Request-Headers"`
ContentSecurityPolicy *string `header:"Content-Security-Policy"`
CrossOriginEmbedderPolicy *string `header:"Cross-Origin-Embedder-Policy"`
PermissionsPolicy *string `header:"Permissions-Policy"`
ProxyAuthorization *string `header:"Proxy-Authorization"`
WWWAuthenticate *string `header:"WWW-Authenticate"`
}
-73
View File
@@ -1,73 +0,0 @@
package ctx
import (
"bytes"
"net/http"
"time"
"github.com/a-h/templ"
"github.com/labstack/echo/v4"
)
// ╭───────────────────────────────────────────────────────────╮
// │ Template Rendering │
// ╰───────────────────────────────────────────────────────────╯
func RenderTempl(c echo.Context, cmp templ.Component) error {
// Create a buffer to store the rendered HTML
buf := &bytes.Buffer{}
// Render the component to the buffer
err := cmp.Render(c.Request().Context(), buf)
if err != nil {
return err
}
// Set the content type
c.Response().Header().Set(echo.HeaderContentType, echo.MIMETextHTML)
// Write the buffered content to the response
_, err = c.Response().Write(buf.Bytes())
return err
}
// ╭──────────────────────────────────────────────────────────╮
// │ Cookie Management │
// ╰──────────────────────────────────────────────────────────╯
func ReadCookie(c echo.Context, key CookieKey) (string, error) {
cookie, err := c.Cookie(key.String())
if err != nil {
// Cookie not found or other error
return "", err
}
if cookie == nil || cookie.Value == "" {
// Cookie is empty
return "", http.ErrNoCookie
}
return cookie.Value, nil
}
func WriteCookie(c echo.Context, key CookieKey, value string) error {
cookie := &http.Cookie{
Name: key.String(),
Value: value,
Expires: time.Now().Add(24 * time.Hour),
HttpOnly: true,
Path: "/",
// Add Secure and SameSite attributes as needed
}
c.SetCookie(cookie)
return nil
}
// ╭────────────────────────────────────────────────────────╮
// │ HTTP Headers │
// ╰────────────────────────────────────────────────────────╯
func WriteHeader(c echo.Context, key HeaderKey, value string) {
c.Response().Header().Set(key.String(), value)
}
func ReadHeader(c echo.Context, key HeaderKey) string {
return c.Response().Header().Get(key.String())
}
+49
View File
@@ -0,0 +1,49 @@
package httputil
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
// FetchAndDecode makes a GET request to the specified URL and decodes the JSON response into the provided type T
func FetchAndDecode[T any](url string) (*T, error) {
// Create HTTP client
client := &http.Client{}
// Create request
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("error creating request: %w", err)
}
// Set headers
req.Header.Set("Content-Type", "application/json")
// Make the request
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("error making request: %w", err)
}
defer resp.Body.Close()
// Check status code
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
// Read body
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("error reading response body: %w", err)
}
// Decode JSON into generic type
var result T
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("error decoding JSON: %w", err)
}
return &result, nil
}
+1
View File
@@ -0,0 +1 @@
package ipfs
+77
View File
@@ -0,0 +1,77 @@
package cookie
import (
"encoding/base64"
"net/http"
"time"
"github.com/labstack/echo/v4"
)
func Exists(c echo.Context, key Key) bool {
ck, err := c.Cookie(key.String())
if err != nil {
return false
}
return ck != nil
}
func Read(c echo.Context, key Key) (string, error) {
cookie, err := c.Cookie(key.String())
if err != nil {
// Cookie not found or other error
return "", err
}
if cookie == nil || cookie.Value == "" {
// Cookie is empty
return "", http.ErrNoCookie
}
return cookie.Value, nil
}
func ReadBytes(c echo.Context, key Key) ([]byte, error) {
cookie, err := c.Cookie(key.String())
if err != nil {
// Cookie not found or other error
return nil, err
}
if cookie == nil || cookie.Value == "" {
// Cookie is empty
return nil, http.ErrNoCookie
}
return base64.RawURLEncoding.DecodeString(cookie.Value)
}
func ReadUnsafe(c echo.Context, key Key) string {
ck, err := c.Cookie(key.String())
if err != nil {
return ""
}
return ck.Value
}
func Write(c echo.Context, key Key, value string) error {
cookie := &http.Cookie{
Name: key.String(),
Value: value,
Expires: time.Now().Add(24 * time.Hour),
HttpOnly: true,
Path: "/",
// Add Secure and SameSite attributes as needed
}
c.SetCookie(cookie)
return nil
}
func WriteBytes(c echo.Context, key Key, value []byte) error {
cookie := &http.Cookie{
Name: key.String(),
Value: base64.RawURLEncoding.EncodeToString(value),
Expires: time.Now().Add(24 * time.Hour),
HttpOnly: true,
Path: "/",
// Add Secure and SameSite attributes as needed
}
c.SetCookie(cookie)
return nil
}
+38
View File
@@ -0,0 +1,38 @@
package cookie
// Key is a type alias for string.
type Key string
const (
// SessionID is the key for the session ID cookie.
SessionID Key = "session.id"
// SessionChallenge is the key for the session challenge cookie.
SessionChallenge Key = "session.challenge"
// SessionRole is the key for the session role cookie.
SessionRole Key = "session.role"
// SonrAddress is the key for the Sonr address cookie.
SonrAddress Key = "sonr.address"
// SonrKeyshare is the key for the Sonr address cookie.
SonrKeyshare Key = "sonr.keyshare"
// SonrDID is the key for the Sonr DID cookie.
SonrDID Key = "sonr.did"
// UserHandle is the key for the User Handle cookie.
UserHandle Key = "user.handle"
// VaultCID is the key for the Vault CID cookie.
VaultCID Key = "vault.cid"
// VaultSchema is the key for the Vault schema cookie.
VaultSchema Key = "vault.schema"
)
// String returns the string representation of the CookieKey.
func (c Key) String() string {
return string(c)
}
+22
View File
@@ -0,0 +1,22 @@
package header
import "github.com/labstack/echo/v4"
func Equals(c echo.Context, key Key, value string) bool {
return c.Response().Header().Get(key.String()) == value
}
// Exists returns true if the request has the header Key.
func Exists(c echo.Context, key Key) bool {
return c.Response().Header().Get(key.String()) != ""
}
// Read returns the header value for the Key.
func Read(c echo.Context, key Key) string {
return c.Response().Header().Get(key.String())
}
// Write sets the header value for the Key.
func Write(c echo.Context, key Key, value string) {
c.Response().Header().Set(key.String(), value)
}
+29
View File
@@ -0,0 +1,29 @@
package header
type Key string
const (
Authorization Key = "Authorization"
// User Agent
Architecture Key = "Sec-CH-UA-Arch"
Bitness Key = "Sec-CH-UA-Bitness"
FullVersionList Key = "Sec-CH-UA-Full-Version-List"
Mobile Key = "Sec-CH-UA-Mobile"
Model Key = "Sec-CH-UA-Model"
Platform Key = "Sec-CH-UA-Platform"
PlatformVersion Key = "Sec-CH-UA-Platform-Version"
UserAgent Key = "Sec-CH-UA"
// Sonr Injected
ChainID Key = "X-Chain-ID"
IPFSHost Key = "X-Host-IPFS"
SonrAPIURL Key = "X-Sonr-API"
SonrgRPCURL Key = "X-Sonr-GRPC"
SonrRPCURL Key = "X-Sonr-RPC"
SonrWSURL Key = "X-Sonr-WS"
)
func (h Key) String() string {
return string(h)
}
+1
View File
@@ -0,0 +1 @@
package request
+1
View File
@@ -0,0 +1 @@
package request
+1
View File
@@ -0,0 +1 @@
package request
+1
View File
@@ -0,0 +1 @@
package request
+1
View File
@@ -0,0 +1 @@
package response
@@ -0,0 +1 @@
package response
@@ -0,0 +1 @@
package response
+45
View File
@@ -0,0 +1,45 @@
package response
import (
"bytes"
"context"
"github.com/a-h/templ"
"github.com/labstack/echo/v4"
)
// Templ renders a component to the response
func Templ(cmp templ.Component) echo.HandlerFunc {
return func(c echo.Context) error {
// Create a buffer to store the rendered HTML
buf := &bytes.Buffer{}
// Render the component to the buffer
err := cmp.Render(c.Request().Context(), buf)
if err != nil {
return err
}
// Set the content type
c.Response().Header().Set(echo.HeaderContentType, echo.MIMETextHTML)
// Write the buffered content to the response
_, err = c.Response().Write(buf.Bytes())
if err != nil {
return err
}
c.Response().WriteHeader(200)
return nil
}
}
// / TemplRawBytes renders a component to a byte slice
func TemplRawBytes(cmp templ.Component) ([]byte, error) {
// Create a buffer to store the rendered HTML
w := bytes.NewBuffer(nil)
err := cmp.Render(context.Background(), w)
if err != nil {
return nil, err
}
dat := w.Bytes()
return dat, nil
}
+60
View File
@@ -0,0 +1,60 @@
package session
import (
"context"
"net/http"
"github.com/labstack/echo/v4"
"github.com/onsonr/sonr/pkg/common"
"github.com/onsonr/sonr/pkg/common/types"
)
type contextKey string
// Context keys
const (
DataContextKey contextKey = "http_session_data"
)
type Context = common.SessionCtx
// Get returns the session.Context from the echo context.
func Get(c echo.Context) (Context, error) {
ctx, ok := c.(*HTTPContext)
if !ok {
return nil, echo.NewHTTPError(http.StatusInternalServerError, "Session Context not found")
}
return ctx, nil
}
// WithData sets the session data in the context
func WithData(ctx context.Context, data *types.Session) context.Context {
return context.WithValue(ctx, DataContextKey, data)
}
// GetData gets the session data from any context type
func GetData(ctx interface{}) *types.Session {
switch c := ctx.(type) {
case *HTTPContext:
if c != nil {
return c.sessionData
}
case context.Context:
if c != nil {
if val := c.Value(DataContextKey); val != nil {
if httpCtx, ok := val.(*types.Session); ok {
return httpCtx
}
}
}
case echo.Context:
if c != nil {
if httpCtx, ok := c.(*HTTPContext); ok && httpCtx != nil {
return httpCtx.sessionData
}
}
}
// Return empty session rather than nil to prevent nil pointer panics
return &types.Session{}
}
@@ -0,0 +1,73 @@
package session
import (
"encoding/json"
"github.com/labstack/echo/v4"
"github.com/onsonr/sonr/pkg/common"
"github.com/onsonr/sonr/pkg/common/middleware/cookie"
"github.com/onsonr/sonr/pkg/common/middleware/header"
"github.com/onsonr/sonr/pkg/core/dwn"
)
// HwayMiddleware establishes a Session Cookie.
func HwayMiddleware() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
cc := injectSession(c, common.RoleHway)
return next(cc)
}
}
}
// MotrMiddleware establishes a Session Cookie.
func MotrMiddleware(config *dwn.Config) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
err := injectConfig(c, config)
if err != nil {
return err
}
cc := injectSession(c, common.RoleMotr)
return next(cc)
}
}
}
func injectConfig(c echo.Context, config *dwn.Config) error {
header.Write(c, header.IPFSHost, config.IpfsGatewayUrl)
header.Write(c, header.ChainID, config.SonrChainId)
header.Write(c, header.SonrAPIURL, config.SonrApiUrl)
header.Write(c, header.SonrRPCURL, config.SonrRpcUrl)
cookie.Write(c, cookie.SonrAddress, config.MotrAddress)
cookie.Write(c, cookie.SonrKeyshare, config.MotrKeyshare)
schemaBz, err := json.Marshal(config.VaultSchema)
if err != nil {
return err
}
cookie.WriteBytes(c, cookie.VaultSchema, schemaBz)
return nil
}
// injectSession returns the session injectSession from the cookies.
func injectSession(c echo.Context, role common.PeerRole) *HTTPContext {
if c == nil {
return initHTTPContext(nil)
}
cookie.Write(c, cookie.SessionRole, role.String())
// Continue even if there are errors, just ensure we have valid session data
if err := loadOrGenKsuid(c); err != nil {
// Log error but continue
}
if err := loadOrGenChallenge(c); err != nil {
// Log error but continue
}
return initHTTPContext(c)
}
+84
View File
@@ -0,0 +1,84 @@
package session
import (
"time"
"github.com/labstack/echo/v4"
"github.com/onsonr/sonr/pkg/common"
"github.com/onsonr/sonr/pkg/common/middleware/cookie"
"github.com/onsonr/sonr/pkg/common/types"
)
// HTTPContext is the context for HTTP endpoints.
type HTTPContext struct {
echo.Context
role common.PeerRole
sessionData *types.Session
}
// Ensure HTTPContext implements context.Context
func (s *HTTPContext) Deadline() (deadline time.Time, ok bool) {
return s.Context.Request().Context().Deadline()
}
func (s *HTTPContext) Done() <-chan struct{} {
return s.Context.Request().Context().Done()
}
func (s *HTTPContext) Err() error {
return s.Context.Request().Context().Err()
}
func (s *HTTPContext) Value(key interface{}) interface{} {
return s.Context.Request().Context().Value(key)
}
// initHTTPContext loads the headers from the request.
func initHTTPContext(c echo.Context) *HTTPContext {
if c == nil {
return &HTTPContext{
sessionData: &types.Session{},
}
}
sessionData := injectSessionData(c)
if sessionData == nil {
sessionData = &types.Session{}
}
cc := &HTTPContext{
Context: c,
role: common.PeerRole(cookie.ReadUnsafe(c, cookie.SessionRole)),
sessionData: sessionData,
}
// Set the session data in both contexts
c.SetRequest(c.Request().WithContext(WithData(c.Request().Context(), sessionData)))
return cc
}
func (s *HTTPContext) ID() string {
return s.GetData().Id
}
func (s *HTTPContext) LoginOptions(credentials []common.CredDescriptor) *common.LoginOptions {
ch, _ := common.Base64Decode(s.GetData().Challenge)
return &common.LoginOptions{
Challenge: ch,
Timeout: 10000,
AllowedCredentials: credentials,
}
}
func (s *HTTPContext) RegisterOptions(subject string) *common.RegisterOptions {
ch, _ := common.Base64Decode(s.GetData().Challenge)
opts := baseRegisterOptions()
opts.Challenge = ch
opts.User = buildUserEntity(subject)
return opts
}
func (s *HTTPContext) GetData() *types.Session {
return s.sessionData
}
+188
View File
@@ -0,0 +1,188 @@
package session
import (
"regexp"
"strings"
"github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/protocol/webauthncose"
"github.com/labstack/echo/v4"
"github.com/segmentio/ksuid"
"github.com/onsonr/sonr/pkg/common"
"github.com/onsonr/sonr/pkg/common/middleware/cookie"
"github.com/onsonr/sonr/pkg/common/middleware/header"
"github.com/onsonr/sonr/pkg/common/types"
)
const kWebAuthnTimeout = 6000
// ╭───────────────────────────────────────────────────────────╮
// │ Initialization │
// ╰───────────────────────────────────────────────────────────╯
func loadOrGenChallenge(c echo.Context) error {
var (
chal protocol.URLEncodedBase64
chalRaw []byte
err error
)
// Setup genChal function
genChal := func() []byte {
ch, _ := protocol.CreateChallenge()
bz, _ := ch.MarshalJSON()
return bz
}
// Check if there is a session challenge cookie
if !cookie.Exists(c, cookie.SessionChallenge) {
chalRaw = genChal()
cookie.WriteBytes(c, cookie.SessionChallenge, chalRaw)
} else {
chalRaw, err = cookie.ReadBytes(c, cookie.SessionChallenge)
if err != nil {
return err
}
}
// Attempt to read the session challenge from the "session" cookie
err = chal.UnmarshalJSON(chalRaw)
if err != nil {
return err
}
return nil
}
func loadOrGenKsuid(c echo.Context) error {
var (
sessionID string
err error
)
// Setup genKsuid function
genKsuid := func() string {
return ksuid.New().String()
}
// Attempt to read the session ID from the "session" cookie
if ok := cookie.Exists(c, cookie.SessionID); !ok {
sessionID = genKsuid()
} else {
sessionID, err = cookie.Read(c, cookie.SessionID)
if err != nil {
sessionID = genKsuid()
}
}
cookie.Write(c, cookie.SessionID, sessionID)
return nil
}
// ╭───────────────────────────────────────────────────────────╮
// │ Extraction │
// ╰───────────────────────────────────────────────────────────╯
func injectSessionData(c echo.Context) *types.Session {
id, chal := extractPeerInfo(c)
bn, bv := extractBrowserInfo(c)
return &types.Session{
Id: id,
Challenge: chal,
BrowserName: bn,
BrowserVersion: bv,
UserArchitecture: header.Read(c, header.Architecture),
Platform: header.Read(c, header.Platform),
PlatformVersion: header.Read(c, header.PlatformVersion),
DeviceModel: header.Read(c, header.Model),
IsMobile: header.Equals(c, header.Mobile, "?1"),
}
}
func extractPeerInfo(c echo.Context) (string, string) {
var chal protocol.URLEncodedBase64
id, _ := cookie.Read(c, cookie.SessionID)
chalRaw, _ := cookie.ReadBytes(c, cookie.SessionChallenge)
chal.UnmarshalJSON(chalRaw)
return id, common.Base64Encode(chal)
}
func extractBrowserInfo(c echo.Context) (string, string) {
secCHUA := header.Read(c, header.UserAgent)
// If header is empty, return empty BrowserInfo
if secCHUA == "" {
return "N/A", "-1"
}
// Split the header into individual browser entries
var (
name string
ver string
)
entries := strings.Split(strings.TrimSpace(secCHUA), ",")
for _, entry := range entries {
// Remove leading/trailing spaces and quotes
entry = strings.TrimSpace(entry)
// Use regex to extract the browser name and version
re := regexp.MustCompile(`"([^"]+)";v="([^"]+)"`)
matches := re.FindStringSubmatch(entry)
if len(matches) == 3 {
browserName := matches[1]
version := matches[2]
// Skip "Not A;Brand"
if !validBrowser(browserName) {
continue
}
// Store the first valid browser info as fallback
name = browserName
ver = version
}
}
return name, ver
}
func validBrowser(name string) bool {
return name != common.BrowserNameUnknown.String() && name != common.BrowserNameChromium.String()
}
// ╭───────────────────────────────────────────────────────────╮
// │ Authentication │
// ╰───────────────────────────────────────────────────────────╯
func buildUserEntity(userID string) protocol.UserEntity {
return protocol.UserEntity{
ID: userID,
}
}
// returns the base options for registering a new user without challenge or user entity.
func baseRegisterOptions() *common.RegisterOptions {
return &protocol.PublicKeyCredentialCreationOptions{
Timeout: kWebAuthnTimeout,
Attestation: protocol.PreferDirectAttestation,
AuthenticatorSelection: protocol.AuthenticatorSelection{
AuthenticatorAttachment: "platform",
ResidentKey: protocol.ResidentKeyRequirementPreferred,
UserVerification: "preferred",
},
Parameters: []protocol.CredentialParameter{
{
Type: "public-key",
Algorithm: webauthncose.AlgES256,
},
{
Type: "public-key",
Algorithm: webauthncose.AlgES256K,
},
{
Type: "public-key",
Algorithm: webauthncose.AlgEdDSA,
},
},
}
}
@@ -1,5 +1,5 @@
// Code generated from Pkl module `orm`. DO NOT EDIT.
package orm
// Code generated from Pkl module `common.types.ORM`. DO NOT EDIT.
package models
type Account struct {
Id string `pkl:"id" json:"id,omitempty" query:"id"`
@@ -1,5 +1,5 @@
// Code generated from Pkl module `orm`. DO NOT EDIT.
package orm
// Code generated from Pkl module `common.types.ORM`. DO NOT EDIT.
package models
type Asset struct {
Id string `pkl:"id" json:"id,omitempty" query:"id"`
@@ -1,5 +1,5 @@
// Code generated from Pkl module `orm`. DO NOT EDIT.
package orm
// Code generated from Pkl module `common.types.ORM`. DO NOT EDIT.
package models
type Chain struct {
Id string `pkl:"id" json:"id,omitempty" query:"id"`
@@ -1,5 +1,5 @@
// Code generated from Pkl module `orm`. DO NOT EDIT.
package orm
// Code generated from Pkl module `common.types.ORM`. DO NOT EDIT.
package models
type Credential struct {
Id string `pkl:"id" json:"id,omitempty" query:"id"`
@@ -1,12 +1,12 @@
// Code generated from Pkl module `orm`. DO NOT EDIT.
package orm
// Code generated from Pkl module `common.types.ORM`. DO NOT EDIT.
package models
import (
"github.com/onsonr/sonr/pkg/motr/types/orm/keyalgorithm"
"github.com/onsonr/sonr/pkg/motr/types/orm/keycurve"
"github.com/onsonr/sonr/pkg/motr/types/orm/keyencoding"
"github.com/onsonr/sonr/pkg/motr/types/orm/keyrole"
"github.com/onsonr/sonr/pkg/motr/types/orm/keytype"
"github.com/onsonr/sonr/pkg/common/models/keyalgorithm"
"github.com/onsonr/sonr/pkg/common/models/keycurve"
"github.com/onsonr/sonr/pkg/common/models/keyencoding"
"github.com/onsonr/sonr/pkg/common/models/keyrole"
"github.com/onsonr/sonr/pkg/common/models/keytype"
)
type DID struct {
@@ -1,5 +1,5 @@
// Code generated from Pkl module `orm`. DO NOT EDIT.
package orm
// Code generated from Pkl module `common.types.ORM`. DO NOT EDIT.
package models
type Grant struct {
Id uint `pkl:"id" json:"id,omitempty" query:"id"`
@@ -1,5 +1,5 @@
// Code generated from Pkl module `orm`. DO NOT EDIT.
package orm
// Code generated from Pkl module `common.types.ORM`. DO NOT EDIT.
package models
type JWK struct {
Kty string `pkl:"kty" json:"kty,omitempty"`
@@ -1,5 +1,5 @@
// Code generated from Pkl module `orm`. DO NOT EDIT.
package orm
// Code generated from Pkl module `common.types.ORM`. DO NOT EDIT.
package models
type Keyshare struct {
Id string `pkl:"id" json:"id,omitempty" query:"id"`
@@ -1,5 +1,5 @@
// Code generated from Pkl module `orm`. DO NOT EDIT.
package orm
// Code generated from Pkl module `common.types.ORM`. DO NOT EDIT.
package models
import (
"context"
@@ -7,14 +7,14 @@ import (
"github.com/apple/pkl-go/pkl"
)
type Orm struct {
type ORM struct {
DbName string `pkl:"db_name"`
DbVersion int `pkl:"db_version"`
}
// LoadFromPath loads the pkl module at the given path and evaluates it into a Orm
func LoadFromPath(ctx context.Context, path string) (ret *Orm, err error) {
// LoadFromPath loads the pkl module at the given path and evaluates it into a ORM
func LoadFromPath(ctx context.Context, path string) (ret *ORM, err error) {
evaluator, err := pkl.NewEvaluator(ctx, pkl.PreconfiguredOptions)
if err != nil {
return nil, err
@@ -29,9 +29,9 @@ func LoadFromPath(ctx context.Context, path string) (ret *Orm, err error) {
return ret, err
}
// Load loads the pkl module at the given source and evaluates it with the given evaluator into a Orm
func Load(ctx context.Context, evaluator pkl.Evaluator, source *pkl.ModuleSource) (*Orm, error) {
var ret Orm
// Load loads the pkl module at the given source and evaluates it with the given evaluator into a ORM
func Load(ctx context.Context, evaluator pkl.Evaluator, source *pkl.ModuleSource) (*ORM, error) {
var ret ORM
if err := evaluator.EvaluateModule(ctx, source, &ret); err != nil {
return nil, err
}
@@ -1,5 +1,5 @@
// Code generated from Pkl module `orm`. DO NOT EDIT.
package orm
// Code generated from Pkl module `common.types.ORM`. DO NOT EDIT.
package models
type Profile struct {
Id string `pkl:"id" json:"id,omitempty" query:"id"`
@@ -1,4 +1,4 @@
// Code generated from Pkl module `orm`. DO NOT EDIT.
// Code generated from Pkl module `common.types.ORM`. DO NOT EDIT.
package assettype
import (
@@ -1,4 +1,4 @@
// Code generated from Pkl module `orm`. DO NOT EDIT.
// Code generated from Pkl module `common.types.ORM`. DO NOT EDIT.
package didmethod
import (
+17
View File
@@ -0,0 +1,17 @@
// Code generated from Pkl module `common.types.ORM`. DO NOT EDIT.
package models
import "github.com/apple/pkl-go/pkl"
func init() {
pkl.RegisterMapping("common.types.ORM", ORM{})
pkl.RegisterMapping("common.types.ORM#Account", Account{})
pkl.RegisterMapping("common.types.ORM#Asset", Asset{})
pkl.RegisterMapping("common.types.ORM#Chain", Chain{})
pkl.RegisterMapping("common.types.ORM#Credential", Credential{})
pkl.RegisterMapping("common.types.ORM#DID", DID{})
pkl.RegisterMapping("common.types.ORM#JWK", JWK{})
pkl.RegisterMapping("common.types.ORM#Grant", Grant{})
pkl.RegisterMapping("common.types.ORM#Keyshare", Keyshare{})
pkl.RegisterMapping("common.types.ORM#Profile", Profile{})
}
@@ -1,4 +1,4 @@
// Code generated from Pkl module `orm`. DO NOT EDIT.
// Code generated from Pkl module `common.types.ORM`. DO NOT EDIT.
package keyalgorithm
import (
@@ -1,4 +1,4 @@
// Code generated from Pkl module `orm`. DO NOT EDIT.
// Code generated from Pkl module `common.types.ORM`. DO NOT EDIT.
package keycurve
import (
@@ -1,4 +1,4 @@
// Code generated from Pkl module `orm`. DO NOT EDIT.
// Code generated from Pkl module `common.types.ORM`. DO NOT EDIT.
package keyencoding
import (
@@ -1,4 +1,4 @@
// Code generated from Pkl module `orm`. DO NOT EDIT.
// Code generated from Pkl module `common.types.ORM`. DO NOT EDIT.
package keyrole
import (
@@ -1,4 +1,4 @@
// Code generated from Pkl module `orm`. DO NOT EDIT.
// Code generated from Pkl module `common.types.ORM`. DO NOT EDIT.
package keysharerole
import (
@@ -1,4 +1,4 @@
// Code generated from Pkl module `orm`. DO NOT EDIT.
// Code generated from Pkl module `common.types.ORM`. DO NOT EDIT.
package keytype
import (
@@ -1,4 +1,4 @@
// Code generated from Pkl module `orm`. DO NOT EDIT.
// Code generated from Pkl module `common.types.ORM`. DO NOT EDIT.
package permissiongrant
import (
@@ -1,4 +1,4 @@
// Code generated from Pkl module `orm`. DO NOT EDIT.
// Code generated from Pkl module `common.types.ORM`. DO NOT EDIT.
package permissionscope
import (
@@ -1,4 +1,4 @@
package orm
package common
import (
"reflect"
+69
View File
@@ -0,0 +1,69 @@
package common
import (
"encoding/base64"
"net/http"
"github.com/go-webauthn/webauthn/protocol"
"github.com/labstack/echo/v4"
"github.com/onsonr/sonr/pkg/common/types"
)
var (
ErrInvalidCredentials = echo.NewHTTPError(http.StatusUnauthorized, "Invalid credentials")
ErrInvalidSubject = echo.NewHTTPError(http.StatusBadRequest, "Invalid subject")
ErrInvalidUser = echo.NewHTTPError(http.StatusBadRequest, "Invalid user")
ErrUserAlreadyExists = echo.NewHTTPError(http.StatusConflict, "User already exists")
ErrUserNotFound = echo.NewHTTPError(http.StatusNotFound, "User not found")
)
type SessionCtx interface {
ID() string
LoginOptions(credentials []CredDescriptor) *LoginOptions
RegisterOptions(subject string) *RegisterOptions
GetData() *types.Session
}
type (
CredDescriptor = protocol.CredentialDescriptor
LoginOptions = protocol.PublicKeyCredentialRequestOptions
RegisterOptions = protocol.PublicKeyCredentialCreationOptions
)
type BrowserName string
const (
BrowserNameUnknown BrowserName = " Not A;Brand"
BrowserNameChromium BrowserName = "Chromium"
)
func (n BrowserName) String() string {
return string(n)
}
type PeerRole string
const (
RoleUnknown PeerRole = "none"
RoleHway PeerRole = "hway"
RoleMotr PeerRole = "motr"
)
func (r PeerRole) Is(role PeerRole) bool {
return r == role
}
func (r PeerRole) String() string {
return string(r)
}
func Base64Encode(data []byte) string {
return base64.RawURLEncoding.EncodeToString(data)
}
func Base64Decode(data string) ([]byte, error) {
return base64.RawURLEncoding.DecodeString(data)
}
+36
View File
@@ -0,0 +1,36 @@
// Code generated from Pkl module `common.types.Ctx`. DO NOT EDIT.
package types
import (
"context"
"github.com/apple/pkl-go/pkl"
)
type Ctx struct {
}
// LoadFromPath loads the pkl module at the given path and evaluates it into a Ctx
func LoadFromPath(ctx context.Context, path string) (ret *Ctx, err error) {
evaluator, err := pkl.NewEvaluator(ctx, pkl.PreconfiguredOptions)
if err != nil {
return nil, err
}
defer func() {
cerr := evaluator.Close()
if err == nil {
err = cerr
}
}()
ret, err = Load(ctx, evaluator, pkl.FileSource(path))
return ret, err
}
// Load loads the pkl module at the given source and evaluates it with the given evaluator into a Ctx
func Load(ctx context.Context, evaluator pkl.Evaluator, source *pkl.ModuleSource) (*Ctx, error) {
var ret Ctx
if err := evaluator.EvaluateModule(ctx, source, &ret); err != nil {
return nil, err
}
return &ret, nil
}
+24
View File
@@ -0,0 +1,24 @@
// Code generated from Pkl module `common.types.Ctx`. DO NOT EDIT.
package types
type Session struct {
Id string `pkl:"id" json:"id,omitempty"`
Challenge string `pkl:"challenge" json:"challenge,omitempty"`
BrowserName string `pkl:"browserName" json:"browserName,omitempty"`
BrowserVersion string `pkl:"browserVersion" json:"browserVersion,omitempty"`
UserArchitecture string `pkl:"userArchitecture" json:"userArchitecture,omitempty"`
Platform string `pkl:"platform" json:"platform,omitempty"`
PlatformVersion string `pkl:"platformVersion" json:"platformVersion,omitempty"`
DeviceModel string `pkl:"deviceModel" json:"deviceModel,omitempty"`
IsMobile bool `pkl:"isMobile" json:"isMobile,omitempty"`
VaultAddress string `pkl:"vaultAddress" json:"vaultAddress,omitempty"`
}
+9
View File
@@ -0,0 +1,9 @@
// Code generated from Pkl module `common.types.Ctx`. DO NOT EDIT.
package types
import "github.com/apple/pkl-go/pkl"
func init() {
pkl.RegisterMapping("common.types.Ctx", Ctx{})
pkl.RegisterMapping("common.types.Ctx#Session", Session{})
}
-64
View File
@@ -1,64 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc (unknown)
// source: common/v1/ipfs.proto
package commonv1
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
var File_common_v1_ipfs_proto protoreflect.FileDescriptor
var file_common_v1_ipfs_proto_rawDesc = []byte{
0x0a, 0x14, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x70, 0x66, 0x73,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76,
0x31, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
0x6f, 0x6e, 0x73, 0x6f, 0x6e, 0x72, 0x2f, 0x73, 0x6f, 0x6e, 0x72, 0x2f, 0x70, 0x6b, 0x67, 0x2f,
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x3b, 0x63, 0x6f, 0x6d,
0x6d, 0x6f, 0x6e, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var file_common_v1_ipfs_proto_goTypes = []interface{}{}
var file_common_v1_ipfs_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_common_v1_ipfs_proto_init() }
func file_common_v1_ipfs_proto_init() {
if File_common_v1_ipfs_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_common_v1_ipfs_proto_rawDesc,
NumEnums: 0,
NumMessages: 0,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_common_v1_ipfs_proto_goTypes,
DependencyIndexes: file_common_v1_ipfs_proto_depIdxs,
}.Build()
File_common_v1_ipfs_proto = out.File
file_common_v1_ipfs_proto_rawDesc = nil
file_common_v1_ipfs_proto_goTypes = nil
file_common_v1_ipfs_proto_depIdxs = nil
}
-377
View File
@@ -1,377 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc (unknown)
// source: common/v1/keys.proto
package commonv1
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// PubKey defines a public key for a did
type PubKey struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Role string `protobuf:"bytes,1,opt,name=role,proto3" json:"role,omitempty"`
KeyType string `protobuf:"bytes,2,opt,name=key_type,json=keyType,proto3" json:"key_type,omitempty"`
RawKey *RawKey `protobuf:"bytes,3,opt,name=raw_key,json=rawKey,proto3" json:"raw_key,omitempty"`
Jwk *JSONWebKey `protobuf:"bytes,4,opt,name=jwk,proto3" json:"jwk,omitempty"`
}
func (x *PubKey) Reset() {
*x = PubKey{}
if protoimpl.UnsafeEnabled {
mi := &file_common_v1_keys_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PubKey) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PubKey) ProtoMessage() {}
func (x *PubKey) ProtoReflect() protoreflect.Message {
mi := &file_common_v1_keys_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PubKey.ProtoReflect.Descriptor instead.
func (*PubKey) Descriptor() ([]byte, []int) {
return file_common_v1_keys_proto_rawDescGZIP(), []int{0}
}
func (x *PubKey) GetRole() string {
if x != nil {
return x.Role
}
return ""
}
func (x *PubKey) GetKeyType() string {
if x != nil {
return x.KeyType
}
return ""
}
func (x *PubKey) GetRawKey() *RawKey {
if x != nil {
return x.RawKey
}
return nil
}
func (x *PubKey) GetJwk() *JSONWebKey {
if x != nil {
return x.Jwk
}
return nil
}
// JWK represents a JSON Web Key
type JSONWebKey struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Kty string `protobuf:"bytes,1,opt,name=kty,proto3" json:"kty,omitempty"` // Key Type
Crv string `protobuf:"bytes,2,opt,name=crv,proto3" json:"crv,omitempty"` // Curve (for EC and OKP keys)
X string `protobuf:"bytes,3,opt,name=x,proto3" json:"x,omitempty"` // X coordinate (for EC and OKP keys)
Y string `protobuf:"bytes,4,opt,name=y,proto3" json:"y,omitempty"` // Y coordinate (for EC keys)
N string `protobuf:"bytes,5,opt,name=n,proto3" json:"n,omitempty"` // Modulus (for RSA keys)
E string `protobuf:"bytes,6,opt,name=e,proto3" json:"e,omitempty"` // Exponent (for RSA keys)
}
func (x *JSONWebKey) Reset() {
*x = JSONWebKey{}
if protoimpl.UnsafeEnabled {
mi := &file_common_v1_keys_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *JSONWebKey) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*JSONWebKey) ProtoMessage() {}
func (x *JSONWebKey) ProtoReflect() protoreflect.Message {
mi := &file_common_v1_keys_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use JSONWebKey.ProtoReflect.Descriptor instead.
func (*JSONWebKey) Descriptor() ([]byte, []int) {
return file_common_v1_keys_proto_rawDescGZIP(), []int{1}
}
func (x *JSONWebKey) GetKty() string {
if x != nil {
return x.Kty
}
return ""
}
func (x *JSONWebKey) GetCrv() string {
if x != nil {
return x.Crv
}
return ""
}
func (x *JSONWebKey) GetX() string {
if x != nil {
return x.X
}
return ""
}
func (x *JSONWebKey) GetY() string {
if x != nil {
return x.Y
}
return ""
}
func (x *JSONWebKey) GetN() string {
if x != nil {
return x.N
}
return ""
}
func (x *JSONWebKey) GetE() string {
if x != nil {
return x.E
}
return ""
}
type RawKey struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Algorithm string `protobuf:"bytes,1,opt,name=algorithm,proto3" json:"algorithm,omitempty"`
Encoding string `protobuf:"bytes,2,opt,name=encoding,proto3" json:"encoding,omitempty"`
Curve string `protobuf:"bytes,3,opt,name=curve,proto3" json:"curve,omitempty"`
Key []byte `protobuf:"bytes,4,opt,name=key,proto3" json:"key,omitempty"`
}
func (x *RawKey) Reset() {
*x = RawKey{}
if protoimpl.UnsafeEnabled {
mi := &file_common_v1_keys_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *RawKey) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RawKey) ProtoMessage() {}
func (x *RawKey) ProtoReflect() protoreflect.Message {
mi := &file_common_v1_keys_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RawKey.ProtoReflect.Descriptor instead.
func (*RawKey) Descriptor() ([]byte, []int) {
return file_common_v1_keys_proto_rawDescGZIP(), []int{2}
}
func (x *RawKey) GetAlgorithm() string {
if x != nil {
return x.Algorithm
}
return ""
}
func (x *RawKey) GetEncoding() string {
if x != nil {
return x.Encoding
}
return ""
}
func (x *RawKey) GetCurve() string {
if x != nil {
return x.Curve
}
return ""
}
func (x *RawKey) GetKey() []byte {
if x != nil {
return x.Key
}
return nil
}
var File_common_v1_keys_proto protoreflect.FileDescriptor
var file_common_v1_keys_proto_rawDesc = []byte{
0x0a, 0x14, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x2f, 0x6b, 0x65, 0x79, 0x73,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76,
0x31, 0x22, 0x8c, 0x01, 0x0a, 0x06, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04,
0x72, 0x6f, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65,
0x12, 0x19, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2a, 0x0a, 0x07, 0x72,
0x61, 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63,
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x77, 0x4b, 0x65, 0x79, 0x52,
0x06, 0x72, 0x61, 0x77, 0x4b, 0x65, 0x79, 0x12, 0x27, 0x0a, 0x03, 0x6a, 0x77, 0x6b, 0x18, 0x04,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31,
0x2e, 0x4a, 0x53, 0x4f, 0x4e, 0x57, 0x65, 0x62, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6a, 0x77, 0x6b,
0x22, 0x68, 0x0a, 0x0a, 0x4a, 0x53, 0x4f, 0x4e, 0x57, 0x65, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x10,
0x0a, 0x03, 0x6b, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x74, 0x79,
0x12, 0x10, 0x0a, 0x03, 0x63, 0x72, 0x76, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x63,
0x72, 0x76, 0x12, 0x0c, 0x0a, 0x01, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x01, 0x78,
0x12, 0x0c, 0x0a, 0x01, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x01, 0x79, 0x12, 0x0c,
0x0a, 0x01, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x01, 0x6e, 0x12, 0x0c, 0x0a, 0x01,
0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x01, 0x65, 0x22, 0x6a, 0x0a, 0x06, 0x52, 0x61,
0x77, 0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68,
0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74,
0x68, 0x6d, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x14,
0x0a, 0x05, 0x63, 0x75, 0x72, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63,
0x75, 0x72, 0x76, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28,
0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x6e, 0x73, 0x6f, 0x6e, 0x72, 0x2f, 0x73, 0x6f, 0x6e, 0x72,
0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x74, 0x79, 0x70, 0x65,
0x73, 0x3b, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x33,
}
var (
file_common_v1_keys_proto_rawDescOnce sync.Once
file_common_v1_keys_proto_rawDescData = file_common_v1_keys_proto_rawDesc
)
func file_common_v1_keys_proto_rawDescGZIP() []byte {
file_common_v1_keys_proto_rawDescOnce.Do(func() {
file_common_v1_keys_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_v1_keys_proto_rawDescData)
})
return file_common_v1_keys_proto_rawDescData
}
var file_common_v1_keys_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_common_v1_keys_proto_goTypes = []interface{}{
(*PubKey)(nil), // 0: common.v1.PubKey
(*JSONWebKey)(nil), // 1: common.v1.JSONWebKey
(*RawKey)(nil), // 2: common.v1.RawKey
}
var file_common_v1_keys_proto_depIdxs = []int32{
2, // 0: common.v1.PubKey.raw_key:type_name -> common.v1.RawKey
1, // 1: common.v1.PubKey.jwk:type_name -> common.v1.JSONWebKey
2, // [2:2] is the sub-list for method output_type
2, // [2:2] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_common_v1_keys_proto_init() }
func file_common_v1_keys_proto_init() {
if File_common_v1_keys_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_common_v1_keys_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PubKey); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_common_v1_keys_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*JSONWebKey); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_common_v1_keys_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RawKey); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_common_v1_keys_proto_rawDesc,
NumEnums: 0,
NumMessages: 3,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_common_v1_keys_proto_goTypes,
DependencyIndexes: file_common_v1_keys_proto_depIdxs,
MessageInfos: file_common_v1_keys_proto_msgTypes,
}.Build()
File_common_v1_keys_proto = out.File
file_common_v1_keys_proto_rawDesc = nil
file_common_v1_keys_proto_goTypes = nil
file_common_v1_keys_proto_depIdxs = nil
}
-215
View File
@@ -1,215 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc (unknown)
// source: common/v1/uri.proto
package commonv1
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type URI_URIProtocol int32
const (
URI_HTTPS URI_URIProtocol = 0
URI_IPFS URI_URIProtocol = 1
URI_IPNS URI_URIProtocol = 2
URI_DID URI_URIProtocol = 3
)
// Enum value maps for URI_URIProtocol.
var (
URI_URIProtocol_name = map[int32]string{
0: "HTTPS",
1: "IPFS",
2: "IPNS",
3: "DID",
}
URI_URIProtocol_value = map[string]int32{
"HTTPS": 0,
"IPFS": 1,
"IPNS": 2,
"DID": 3,
}
)
func (x URI_URIProtocol) Enum() *URI_URIProtocol {
p := new(URI_URIProtocol)
*p = x
return p
}
func (x URI_URIProtocol) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (URI_URIProtocol) Descriptor() protoreflect.EnumDescriptor {
return file_common_v1_uri_proto_enumTypes[0].Descriptor()
}
func (URI_URIProtocol) Type() protoreflect.EnumType {
return &file_common_v1_uri_proto_enumTypes[0]
}
func (x URI_URIProtocol) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use URI_URIProtocol.Descriptor instead.
func (URI_URIProtocol) EnumDescriptor() ([]byte, []int) {
return file_common_v1_uri_proto_rawDescGZIP(), []int{0, 0}
}
type URI struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Protocol URI_URIProtocol `protobuf:"varint,1,opt,name=protocol,proto3,enum=common.v1.URI_URIProtocol" json:"protocol,omitempty"`
Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
}
func (x *URI) Reset() {
*x = URI{}
if protoimpl.UnsafeEnabled {
mi := &file_common_v1_uri_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *URI) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*URI) ProtoMessage() {}
func (x *URI) ProtoReflect() protoreflect.Message {
mi := &file_common_v1_uri_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use URI.ProtoReflect.Descriptor instead.
func (*URI) Descriptor() ([]byte, []int) {
return file_common_v1_uri_proto_rawDescGZIP(), []int{0}
}
func (x *URI) GetProtocol() URI_URIProtocol {
if x != nil {
return x.Protocol
}
return URI_HTTPS
}
func (x *URI) GetValue() string {
if x != nil {
return x.Value
}
return ""
}
var File_common_v1_uri_proto protoreflect.FileDescriptor
var file_common_v1_uri_proto_rawDesc = []byte{
0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x72, 0x69, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31,
0x22, 0x8a, 0x01, 0x0a, 0x03, 0x55, 0x52, 0x49, 0x12, 0x36, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x6d,
0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x52, 0x49, 0x2e, 0x55, 0x52, 0x49, 0x50, 0x72,
0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c,
0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x35, 0x0a, 0x0b, 0x55, 0x52, 0x49, 0x50, 0x72, 0x6f,
0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x48, 0x54, 0x54, 0x50, 0x53, 0x10, 0x00,
0x12, 0x08, 0x0a, 0x04, 0x49, 0x50, 0x46, 0x53, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x50,
0x4e, 0x53, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x44, 0x49, 0x44, 0x10, 0x03, 0x42, 0x32, 0x5a,
0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x6e, 0x73, 0x6f,
0x6e, 0x72, 0x2f, 0x73, 0x6f, 0x6e, 0x72, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x63, 0x6f, 0x6d, 0x6d,
0x6f, 0x6e, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x3b, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x76,
0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_common_v1_uri_proto_rawDescOnce sync.Once
file_common_v1_uri_proto_rawDescData = file_common_v1_uri_proto_rawDesc
)
func file_common_v1_uri_proto_rawDescGZIP() []byte {
file_common_v1_uri_proto_rawDescOnce.Do(func() {
file_common_v1_uri_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_v1_uri_proto_rawDescData)
})
return file_common_v1_uri_proto_rawDescData
}
var file_common_v1_uri_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_common_v1_uri_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_common_v1_uri_proto_goTypes = []interface{}{
(URI_URIProtocol)(0), // 0: common.v1.URI.URIProtocol
(*URI)(nil), // 1: common.v1.URI
}
var file_common_v1_uri_proto_depIdxs = []int32{
0, // 0: common.v1.URI.protocol:type_name -> common.v1.URI.URIProtocol
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_common_v1_uri_proto_init() }
func file_common_v1_uri_proto_init() {
if File_common_v1_uri_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_common_v1_uri_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*URI); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_common_v1_uri_proto_rawDesc,
NumEnums: 1,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_common_v1_uri_proto_goTypes,
DependencyIndexes: file_common_v1_uri_proto_depIdxs,
EnumInfos: file_common_v1_uri_proto_enumTypes,
MessageInfos: file_common_v1_uri_proto_msgTypes,
}.Build()
File_common_v1_uri_proto = out.File
file_common_v1_uri_proto_rawDesc = nil
file_common_v1_uri_proto_goTypes = nil
file_common_v1_uri_proto_depIdxs = nil
}
@@ -1,5 +1,5 @@
// Code generated from Pkl module `dwn`. DO NOT EDIT.
package config
// Code generated from Pkl module `common.types.DWN`. DO NOT EDIT.
package dwn
type Config struct {
IpfsGatewayUrl string `pkl:"ipfsGatewayUrl" json:"ipfsGatewayUrl,omitempty"`
@@ -1,5 +1,5 @@
// Code generated from Pkl module `dwn`. DO NOT EDIT.
package config
// Code generated from Pkl module `common.types.DWN`. DO NOT EDIT.
package dwn
import (
"context"
@@ -7,11 +7,11 @@ import (
"github.com/apple/pkl-go/pkl"
)
type Dwn struct {
type DWN struct {
}
// LoadFromPath loads the pkl module at the given path and evaluates it into a Dwn
func LoadFromPath(ctx context.Context, path string) (ret *Dwn, err error) {
// LoadFromPath loads the pkl module at the given path and evaluates it into a DWN
func LoadFromPath(ctx context.Context, path string) (ret *DWN, err error) {
evaluator, err := pkl.NewEvaluator(ctx, pkl.PreconfiguredOptions)
if err != nil {
return nil, err
@@ -26,9 +26,9 @@ func LoadFromPath(ctx context.Context, path string) (ret *Dwn, err error) {
return ret, err
}
// Load loads the pkl module at the given source and evaluates it with the given evaluator into a Dwn
func Load(ctx context.Context, evaluator pkl.Evaluator, source *pkl.ModuleSource) (*Dwn, error) {
var ret Dwn
// Load loads the pkl module at the given source and evaluates it with the given evaluator into a DWN
func Load(ctx context.Context, evaluator pkl.Evaluator, source *pkl.ModuleSource) (*DWN, error) {
var ret DWN
if err := evaluator.EvaluateModule(ctx, source, &ret); err != nil {
return nil, err
}
+14
View File
@@ -0,0 +1,14 @@
// Code generated from Pkl module `common.types.DWN`. DO NOT EDIT.
package dwn
type Environment struct {
IsDevelopment bool `pkl:"isDevelopment" json:"isDevelopment,omitempty"`
CacheVersion string `pkl:"cacheVersion" json:"cacheVersion,omitempty"`
HttpserverPath string `pkl:"httpserverPath" json:"httpserverPath,omitempty"`
WasmExecPath string `pkl:"wasmExecPath" json:"wasmExecPath,omitempty"`
WasmPath string `pkl:"wasmPath" json:"wasmPath,omitempty"`
}
@@ -1,5 +1,5 @@
// Code generated from Pkl module `dwn`. DO NOT EDIT.
package config
// Code generated from Pkl module `common.types.DWN`. DO NOT EDIT.
package dwn
type Schema struct {
Version int `pkl:"version"`
+211
View File
@@ -0,0 +1,211 @@
//go:build js && wasm
// +build js,wasm
package bridge
import (
"bytes"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"sync"
"syscall/js"
)
var (
// Global buffer pool to reduce allocations
bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
// Cached JS globals
jsGlobal = js.Global()
jsUint8Array = jsGlobal.Get("Uint8Array")
jsResponse = jsGlobal.Get("Response")
jsPromise = jsGlobal.Get("Promise")
jsWasmHTTP = jsGlobal.Get("wasmhttp")
)
// serveFetch serves HTTP requests with optimized handler management
func ServeFetch(handler http.Handler) func() {
h := handler
if h == nil {
h = http.DefaultServeMux
}
// Optimize prefix handling
prefix := strings.TrimRight(jsWasmHTTP.Get("path").String(), "/")
if prefix != "" {
mux := http.NewServeMux()
mux.Handle(prefix+"/", http.StripPrefix(prefix, h))
h = mux
}
// Create request handler function
cb := js.FuncOf(func(_ js.Value, args []js.Value) interface{} {
promise, resolve, reject := newPromiseOptimized()
go handleRequest(h, args[1], resolve, reject)
return promise
})
jsWasmHTTP.Call("setHandler", cb)
return cb.Release
}
// handleRequest processes the request with panic recovery
func handleRequest(h http.Handler, jsReq js.Value, resolve, reject func(interface{})) {
defer func() {
if r := recover(); r != nil {
var errMsg string
if err, ok := r.(error); ok {
errMsg = fmt.Sprintf("wasmhttp: panic: %+v", err)
} else {
errMsg = fmt.Sprintf("wasmhttp: panic: %v", r)
}
reject(errMsg)
}
}()
recorder := newResponseRecorder()
h.ServeHTTP(recorder, buildRequest(jsReq))
resolve(recorder.jsResponse())
}
// buildRequest creates an http.Request from JS Request
func buildRequest(jsReq js.Value) *http.Request {
// Get request body
arrayBuffer, err := awaitPromiseOptimized(jsReq.Call("arrayBuffer"))
if err != nil {
panic(err)
}
// Create body buffer
jsBody := jsUint8Array.New(arrayBuffer)
bodyLen := jsBody.Get("length").Int()
body := make([]byte, bodyLen)
js.CopyBytesToGo(body, jsBody)
// Create request
req := httptest.NewRequest(
jsReq.Get("method").String(),
jsReq.Get("url").String(),
bytes.NewReader(body),
)
// Set headers efficiently
headers := jsReq.Get("headers")
headersIt := headers.Call("entries")
for {
entry := headersIt.Call("next")
if entry.Get("done").Bool() {
break
}
pair := entry.Get("value")
req.Header.Set(pair.Index(0).String(), pair.Index(1).String())
}
return req
}
// ResponseRecorder with optimized buffer handling
type ResponseRecorder struct {
*httptest.ResponseRecorder
buffer *bytes.Buffer
}
func newResponseRecorder() *ResponseRecorder {
return &ResponseRecorder{
ResponseRecorder: httptest.NewRecorder(),
buffer: bufferPool.Get().(*bytes.Buffer),
}
}
// jsResponse creates a JS Response with optimized memory usage
func (rr *ResponseRecorder) jsResponse() js.Value {
defer func() {
rr.buffer.Reset()
bufferPool.Put(rr.buffer)
}()
res := rr.Result()
defer res.Body.Close()
// Prepare response body
body := js.Undefined()
if res.ContentLength != 0 {
if _, err := io.Copy(rr.buffer, res.Body); err != nil {
panic(err)
}
bodyBytes := rr.buffer.Bytes()
body = jsUint8Array.New(len(bodyBytes))
js.CopyBytesToJS(body, bodyBytes)
}
// Prepare response init object
init := make(map[string]interface{}, 3)
if res.StatusCode != 0 {
init["status"] = res.StatusCode
}
if len(res.Header) > 0 {
headers := make(map[string]interface{}, len(res.Header))
for k, v := range res.Header {
if len(v) > 0 {
headers[k] = v[0]
}
}
init["headers"] = headers
}
return jsResponse.New(body, init)
}
// newPromiseOptimized creates a new JavaScript Promise with optimized callback handling
func newPromiseOptimized() (js.Value, func(interface{}), func(interface{})) {
var (
resolve func(interface{})
reject func(interface{})
promiseFunc = js.FuncOf(func(_ js.Value, args []js.Value) interface{} {
resolve = func(v interface{}) { args[0].Invoke(v) }
reject = func(v interface{}) { args[1].Invoke(v) }
return js.Undefined()
})
)
defer promiseFunc.Release()
return jsPromise.New(promiseFunc), resolve, reject
}
// awaitPromiseOptimized waits for Promise resolution with optimized channel handling
func awaitPromiseOptimized(promise js.Value) (js.Value, error) {
done := make(chan struct{})
var (
result js.Value
err error
)
thenFunc := js.FuncOf(func(_ js.Value, args []js.Value) interface{} {
result = args[0]
close(done)
return nil
})
defer thenFunc.Release()
catchFunc := js.FuncOf(func(_ js.Value, args []js.Value) interface{} {
err = js.Error{Value: args[0]}
close(done)
return nil
})
defer catchFunc.Release()
promise.Call("then", thenFunc).Call("catch", catchFunc)
<-done
return result, err
}
+34
View File
@@ -0,0 +1,34 @@
//go:build js && wasm
// +build js,wasm
package bridge
import (
"encoding/base64"
"encoding/json"
"github.com/labstack/echo/v4"
)
func WasmContextMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// Extract WASM context from headers
if wasmCtx := c.Request().Header.Get("X-Wasm-Context"); wasmCtx != "" {
if ctx, err := DecodeWasmContext(wasmCtx); err == nil {
c.Set("wasm_context", ctx)
}
}
return next(c)
}
}
// decodeWasmContext decodes the WASM context from a base64 encoded string
func DecodeWasmContext(ctx string) (map[string]any, error) {
decoded, err := base64.StdEncoding.DecodeString(ctx)
if err != nil {
return nil, err
}
var ctxData map[string]any
err = json.Unmarshal(decoded, &ctxData)
return ctxData, err
}
+30
View File
@@ -0,0 +1,30 @@
package dwn
import (
"encoding/json"
"os"
)
const dwnJSONFileName = "dwn.json"
func LoadJSONConfig() (*Config, error) {
// Read dwn.json config
dwnBz, err := os.ReadFile(dwnJSONFileName)
if err != nil {
return nil, err
}
dwnConfig := new(Config)
err = json.Unmarshal(dwnBz, dwnConfig)
if err != nil {
return nil, err
}
return dwnConfig, nil
}
func (c *Config) MarshalJSON() ([]byte, error) {
return json.Marshal(c)
}
func (c *Config) UnmarshalJSON(data []byte) error {
return json.Unmarshal(data, c)
}
@@ -3,8 +3,6 @@ package handlers
import (
"github.com/go-webauthn/webauthn/protocol"
"github.com/labstack/echo/v4"
"github.com/onsonr/sonr/pkg/motr/types/orm"
)
// ╭───────────────────────────────────────────────────────────╮
@@ -47,15 +45,11 @@ func RegisterSubjectCheck(e echo.Context) error {
// RegisterSubjectStart handles the register subject start.
func RegisterSubjectStart(e echo.Context) error {
// Get subject and address
subject := e.FormValue("subject")
address := e.FormValue("address")
// subject := e.FormValue("subject")
// Get challenge
chal, err := protocol.CreateChallenge()
if err != nil {
return err
}
return e.JSON(201, orm.NewCredentialCreationOptions(subject, address, chal))
return nil
}
// RegisterSubjectFinish handles the register subject finish.
+284
View File
@@ -0,0 +1,284 @@
package handlers
import (
"fmt"
"net/http"
"github.com/labstack/echo/v4"
"github.com/onsonr/sonr/pkg/core/dwn"
)
// generateRawServiceWorkerJS returns the service worker JavaScript as a string
func generateRawServiceWorkerJS(cfg *dwn.Environment) string {
return fmt.Sprintf(`const CACHE_NAMES = {
wasm: "wasm-cache-%s",
static: "static-cache-%s",
dynamic: "dynamic-cache-%s"
};
importScripts(
%q,
%q
);
// Initialize WASM HTTP listener with configured path
const wasmInstance = registerWasmHTTPListener(%q);
// MessageChannel port for WASM communication
let wasmPort;
// Request queue for offline operations
let requestQueue = new Map();
// Setup message channel handler
self.addEventListener('message', async (event) => {
if (event.data.type === 'PORT_INITIALIZATION') {
wasmPort = event.data.port;
setupWasmCommunication();
}
});
function setupWasmCommunication() {
wasmPort.onmessage = async (event) => {
const { type, data } = event.data;
switch (type) {
case 'WASM_REQUEST':
handleWasmRequest(data);
break;
case 'SYNC_REQUEST':
processSyncQueue();
break;
}
};
// Notify that WASM is ready
wasmPort.postMessage({ type: 'WASM_READY' });
}
// Enhanced install event
self.addEventListener("install", (event) => {
event.waitUntil(
Promise.all([
skipWaiting(),
// Cache WASM binary and essential resources
caches.open(CACHE_NAMES.wasm).then(cache =>
cache.addAll([
%q,
%q
])
)
])
);
});
// Enhanced activate event
self.addEventListener("activate", (event) => {
event.waitUntil(
Promise.all([
clients.claim(),
// Clean up old caches
caches.keys().then(keys =>
Promise.all(
keys.map(key => {
if (!Object.values(CACHE_NAMES).includes(key)) {
return caches.delete(key);
}
})
)
)
])
);
});
// Intercept fetch events
self.addEventListener('fetch', (event) => {
const request = event.request;
// Handle API requests differently from static resources
if (request.url.includes('/api/')) {
event.respondWith(handleApiRequest(request));
} else {
event.respondWith(handleStaticRequest(request));
}
});
async function handleApiRequest(request) {
try {
// Try to make the request
const response = await fetch(request.clone());
// If successful, pass through WASM handler
if (response.ok) {
return await processWasmResponse(request, response);
}
// If offline or failed, queue the request
await queueRequest(request);
// Return cached response if available
const cachedResponse = await caches.match(request);
if (cachedResponse) {
return cachedResponse;
}
// Return offline response
return new Response(
JSON.stringify({ error: 'Currently offline' }),
{
status: 503,
headers: { 'Content-Type': 'application/json' }
}
);
} catch (error) {
await queueRequest(request);
return new Response(
JSON.stringify({ error: 'Request failed' }),
{
status: 500,
headers: { 'Content-Type': 'application/json' }
}
);
}
}
async function handleStaticRequest(request) {
// Check cache first
const cachedResponse = await caches.match(request);
if (cachedResponse) {
return cachedResponse;
}
try {
const response = await fetch(request);
// Cache successful responses
if (response.ok) {
const cache = await caches.open(CACHE_NAMES.static);
cache.put(request, response.clone());
}
return response;
} catch (error) {
// Return offline page for navigation requests
if (request.mode === 'navigate') {
return caches.match('/offline.html');
}
throw error;
}
}
async function processWasmResponse(request, response) {
const responseClone = response.clone();
try {
const processedResponse = await wasmInstance.processResponse(responseClone);
if (wasmPort) {
wasmPort.postMessage({
type: 'RESPONSE',
requestId: request.headers.get('X-Wasm-Request-ID'),
response: processedResponse
});
}
return processedResponse;
} catch (error) {
console.error('WASM processing error:', error);
return response;
}
}
async function queueRequest(request) {
const serializedRequest = await serializeRequest(request);
requestQueue.set(request.url, serializedRequest);
try {
await self.registration.sync.register('wasm-sync');
} catch (error) {
console.error('Sync registration failed:', error);
}
}
async function serializeRequest(request) {
const headers = {};
for (const [key, value] of request.headers.entries()) {
headers[key] = value;
}
return {
url: request.url,
method: request.method,
headers,
body: await request.text(),
timestamp: Date.now()
};
}
// Handle background sync
self.addEventListener('sync', (event) => {
if (event.tag === 'wasm-sync') {
event.waitUntil(processSyncQueue());
}
});
async function processSyncQueue() {
const requests = Array.from(requestQueue.values());
for (const serializedRequest of requests) {
try {
const response = await fetch(new Request(serializedRequest.url, {
method: serializedRequest.method,
headers: serializedRequest.headers,
body: serializedRequest.body
}));
if (response.ok) {
requestQueue.delete(serializedRequest.url);
if (wasmPort) {
wasmPort.postMessage({
type: 'SYNC_COMPLETE',
url: serializedRequest.url
});
}
}
} catch (error) {
console.error('Sync failed for request:', error);
}
}
}
// Handle payment requests
self.addEventListener("canmakepayment", function (e) {
e.respondWith(Promise.resolve(true));
});
// Handle periodic sync if available
self.addEventListener('periodicsync', (event) => {
if (event.tag === 'wasm-sync') {
event.waitUntil(processSyncQueue());
}
});`,
cfg.CacheVersion,
cfg.CacheVersion,
cfg.CacheVersion,
cfg.WasmExecPath,
cfg.HttpserverPath,
cfg.WasmPath,
cfg.WasmPath,
cfg.WasmExecPath,
)
}
// ServiceWorkerHandler is an Echo handler that serves the service worker
func ServiceWorkerHandler(cfg *dwn.Environment) echo.HandlerFunc {
return func(c echo.Context) error {
// Set appropriate headers for service worker
c.Response().Header().Set("Content-Type", "application/javascript")
c.Response().Header().Set("Service-Worker-Allowed", "/")
// Generate and write the service worker JavaScript
return c.String(http.StatusOK, generateRawServiceWorkerJS(cfg))
}
}
+11
View File
@@ -0,0 +1,11 @@
// Code generated from Pkl module `common.types.DWN`. DO NOT EDIT.
package dwn
import "github.com/apple/pkl-go/pkl"
func init() {
pkl.RegisterMapping("common.types.DWN", DWN{})
pkl.RegisterMapping("common.types.DWN#Config", Config{})
pkl.RegisterMapping("common.types.DWN#Schema", Schema{})
pkl.RegisterMapping("common.types.DWN#Environment", Environment{})
}
+60
View File
@@ -0,0 +1,60 @@
//go:build js && wasm
// +build js,wasm
package server
import (
"github.com/labstack/echo/v4"
"github.com/onsonr/sonr/pkg/common/middleware/session"
"github.com/onsonr/sonr/pkg/core/dwn"
"github.com/onsonr/sonr/pkg/core/dwn/bridge"
"github.com/onsonr/sonr/pkg/core/dwn/handlers"
)
// Server is the interface that wraps the Serve function.
type Server interface {
Serve() func()
}
type MotrServer struct {
e *echo.Echo
WasmPath string
WasmExecPath string
HTTPServerPath string
CacheVersion string
IsDev bool
}
func New(env *dwn.Environment, config *dwn.Config) Server {
s := &MotrServer{e: echo.New()}
s.e.Use(session.MotrMiddleware(config))
s.e.Use(bridge.WasmContextMiddleware)
// Add WASM-specific routes
registerAPI(s.e)
return s
}
func (s *MotrServer) Serve() func() {
return bridge.ServeFetch(s.e)
}
// registerAPI registers the Decentralized Web Node API routes.
func registerAPI(e *echo.Echo) {
g1 := e.Group("api")
g1.GET("/register/:subject/start", handlers.RegisterSubjectStart)
g1.POST("/register/:subject/check", handlers.RegisterSubjectCheck)
g1.POST("/register/:subject/finish", handlers.RegisterSubjectFinish)
g1.GET("/login/:subject/start", handlers.LoginSubjectStart)
g1.POST("/login/:subject/check", handlers.LoginSubjectCheck)
g1.POST("/login/:subject/finish", handlers.LoginSubjectFinish)
g1.GET("/:origin/grant/jwks", handlers.GetJWKS)
g1.GET("/:origin/grant/token", handlers.GetToken)
g1.POST("/:origin/grant/:subject", handlers.GrantAuthorization)
}
+171
View File
@@ -0,0 +1,171 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
// Package accumulator implements the cryptographic accumulator as described in https://eprint.iacr.org/2020/777.pdf
// It also implements the zero knowledge proof of knowledge protocol
// described in section 7 of the paper.
// Note: the paper only describes for non-membership witness case, but we don't
// use non-membership witness. We only implement the membership witness case.
package accumulator
import (
"fmt"
"git.sr.ht/~sircmpwn/go-bare"
"github.com/onsonr/sonr/pkg/crypto/core/curves"
)
type structMarshal struct {
Curve string `bare:"curve"`
Value []byte `bare:"value"`
}
type Element curves.Scalar
// Coefficient is a point
type Coefficient curves.Point
// Accumulator is a point
type Accumulator struct {
value curves.Point
}
// New creates a new accumulator.
func (acc *Accumulator) New(curve *curves.PairingCurve) (*Accumulator, error) {
// If we need to support non-membership witness, we need to implement Accumulator Initialization
// as described in section 6 of <https://eprint.iacr.org/2020/777.pdf>
// for now we don't need non-membership witness
// i.e., it computes V0 = prod(y + α) * P, y ∈ Y_V0, P is a generator of G1. Since we do not use non-membership witness
// we just set the initial accumulator a G1 generator.
acc.value = curve.Scalar.Point().Generator()
return acc, nil
}
// WithElements initializes a new accumulator prefilled with entries
// Each member is assumed to be hashed
// V = prod(y + α) * V0, for all y∈ Y_V
func (acc *Accumulator) WithElements(curve *curves.PairingCurve, key *SecretKey, m []Element) (*Accumulator, error) {
_, err := acc.New(curve)
if err != nil {
return nil, err
}
y, err := key.BatchAdditions(m)
if err != nil {
return nil, err
}
acc.value = acc.value.Mul(y)
return acc, nil
}
// AddElements accumulates a set of elements into the accumulator.
func (acc *Accumulator) AddElements(key *SecretKey, m []Element) (*Accumulator, error) {
if acc.value == nil || key.value == nil {
return nil, fmt.Errorf("accumulator and secret key should not be nil")
}
y, err := key.BatchAdditions(m)
if err != nil {
return nil, err
}
acc.value = acc.value.Mul(y)
return acc, nil
}
// Add accumulates a single element into the accumulator
// V' = (y + alpha) * V
func (acc *Accumulator) Add(key *SecretKey, e Element) (*Accumulator, error) {
if acc.value == nil || acc.value.IsIdentity() || key.value == nil || e == nil {
return nil, fmt.Errorf("accumulator, secret key and element should not be nil")
}
y := e.Add(key.value) // y + alpha
acc.value = acc.value.Mul(y)
return acc, nil
}
// Remove removes a single element from accumulator if it exists
// V' = 1/(y+alpha) * V
func (acc *Accumulator) Remove(key *SecretKey, e Element) (*Accumulator, error) {
if acc.value == nil || acc.value.IsIdentity() || key.value == nil || e == nil {
return nil, fmt.Errorf("accumulator, secret key and element should not be nil")
}
y := e.Add(key.value) // y + alpha
y, err := y.Invert() // 1/(y+alpha)
if err != nil {
return nil, err
}
acc.value = acc.value.Mul(y)
return acc, nil
}
// Update performs a batch addition and deletion as described on page 7, section 3 in
// https://eprint.iacr.org/2020/777.pdf
func (acc *Accumulator) Update(key *SecretKey, additions []Element, deletions []Element) (*Accumulator, []Coefficient, error) {
if acc.value == nil || acc.value.IsIdentity() || key.value == nil {
return nil, nil, fmt.Errorf("accumulator and secret key should not be nil")
}
// Compute dA(-alpha) = prod(y + alpha), y in the set of A ⊆ ACC-Y_V
a, err := key.BatchAdditions(additions)
if err != nil {
return nil, nil, err
}
// Compute dD(-alpha) = 1/prod(y + alpha), y in the set of D ⊆ Y_V
d, err := key.BatchDeletions(deletions)
if err != nil {
return nil, nil, err
}
// dA(-alpha)/dD(-alpha)
div := a.Mul(d)
newAcc := acc.value.Mul(div)
// build an array of coefficients
elements, err := key.CreateCoefficients(additions, deletions)
if err != nil {
return nil, nil, err
}
coefficients := make([]Coefficient, len(elements))
for i := 0; i < len(elements); i++ {
coefficients[i] = acc.value.Mul(elements[i])
}
acc.value = newAcc
return acc, coefficients, nil
}
// MarshalBinary converts Accumulator to bytes
func (acc Accumulator) MarshalBinary() ([]byte, error) {
if acc.value == nil {
return nil, fmt.Errorf("accumulator cannot be nil")
}
tv := &structMarshal{
Value: acc.value.ToAffineCompressed(),
Curve: acc.value.CurveName(),
}
return bare.Marshal(tv)
}
// UnmarshalBinary sets Accumulator from bytes
func (acc *Accumulator) UnmarshalBinary(data []byte) error {
tv := new(structMarshal)
err := bare.Unmarshal(data, tv)
if err != nil {
return err
}
curve := curves.GetCurveByName(tv.Curve)
if curve == nil {
return fmt.Errorf("invalid curve")
}
value, err := curve.NewIdentityPoint().FromAffineCompressed(tv.Value)
if err != nil {
return err
}
acc.value = value
return nil
}
+188
View File
@@ -0,0 +1,188 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package accumulator
import (
"encoding/hex"
"fmt"
"testing"
"github.com/stretchr/testify/require"
"github.com/onsonr/sonr/pkg/crypto/core/curves"
)
func TestNewAccumulator100(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
var seed [32]byte
key, err := new(SecretKey).New(curve, seed[:])
require.NoError(t, err)
require.NotNil(t, key)
acc, err := new(Accumulator).New(curve)
require.NoError(t, err)
accBz, err := acc.MarshalBinary()
require.NoError(t, err)
fmt.Println(accBz)
fmt.Println(len(accBz))
fmt.Println(hex.EncodeToString(accBz))
fmt.Println(len(hex.EncodeToString(accBz)))
require.Equal(t, 60, len(accBz), "Marshalled accumulator should be 60 bytes")
require.Equal(t, 120, len(hex.EncodeToString(accBz)), "Hex-encoded accumulator should be 120 characters")
require.NotNil(t, acc)
require.Equal(t, acc.value.ToAffineCompressed(), curve.PointG1.Generator().ToAffineCompressed())
}
func TestNewAccumulator10K(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
var seed [32]byte
key, err := new(SecretKey).New(curve, seed[:])
require.NoError(t, err)
require.NotNil(t, key)
acc, err := new(Accumulator).New(curve)
require.NoError(t, err)
require.NotNil(t, acc)
require.Equal(t, acc.value.ToAffineCompressed(), curve.PointG1.Generator().ToAffineCompressed())
}
func TestNewAccumulator10M(t *testing.T) {
// Initiating 10M values takes time
if testing.Short() {
t.Skip("skipping test in short mode.")
}
curve := curves.BLS12381(&curves.PointBls12381G1{})
var seed [32]byte
key, err := new(SecretKey).New(curve, seed[:])
require.NoError(t, err)
require.NotNil(t, key)
acc, err := new(Accumulator).New(curve)
require.NoError(t, err)
require.NotNil(t, acc)
require.Equal(t, acc.value.ToAffineCompressed(), curve.PointG1.Generator().ToAffineCompressed())
}
func TestWithElements(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
var seed [32]byte
key, _ := new(SecretKey).New(curve, seed[:])
element1 := curve.Scalar.Hash([]byte("value1"))
element2 := curve.Scalar.Hash([]byte("value2"))
elements := []Element{element1, element2}
newAcc, err := new(Accumulator).WithElements(curve, key, elements)
require.NoError(t, err)
require.NotNil(t, newAcc)
require.NotEqual(t, newAcc.value.ToAffineCompressed(), curve.PointG1.Identity().ToAffineCompressed())
require.NotEqual(t, newAcc.value.ToAffineCompressed(), curve.PointG1.Generator().ToAffineCompressed())
_, _ = newAcc.Remove(key, element1)
_, _ = newAcc.Remove(key, element2)
require.Equal(t, newAcc.value.ToAffineCompressed(), curve.PointG1.Generator().ToAffineCompressed())
}
func TestAdd(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
var seed [32]byte
key, err := new(SecretKey).New(curve, seed[:])
require.NoError(t, err)
require.NotNil(t, key)
acc := &Accumulator{curve.PointG1.Generator()}
_, _ = acc.New(curve)
require.NoError(t, err)
require.NotNil(t, acc)
element := curve.Scalar.Hash([]byte("value1"))
require.NoError(t, err)
require.NotNil(t, element)
_, _ = acc.Add(key, element)
require.NotEqual(t, acc.value.ToAffineCompressed(), curve.PointG1.Generator().ToAffineCompressed())
}
func TestRemove(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
var seed [32]byte
key, err := new(SecretKey).New(curve, seed[:])
require.NoError(t, err)
require.NotNil(t, key)
acc, err := new(Accumulator).New(curve)
require.NoError(t, err)
require.NotNil(t, acc)
require.Equal(t, acc.value.ToAffineCompressed(), curve.PointG1.Generator().ToAffineCompressed())
element := curve.Scalar.Hash([]byte("value1"))
require.NoError(t, err)
require.NotNil(t, element)
// add element
_, _ = acc.Add(key, element)
require.NotEqual(t, acc.value.ToAffineCompressed(), curve.PointG1.Generator().ToAffineCompressed())
// remove element
acc, err = acc.Remove(key, element)
require.NoError(t, err)
require.Equal(t, acc.value.ToAffineCompressed(), curve.PointG1.Generator().ToAffineCompressed())
}
func TestAddElements(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
var seed [32]byte
key, err := new(SecretKey).New(curve, seed[:])
require.NoError(t, err)
require.NotNil(t, key)
acc := &Accumulator{curve.PointG1.Generator()}
_, _ = acc.New(curve)
require.NoError(t, err)
require.NotNil(t, acc)
require.Equal(t, acc.value.ToAffineCompressed(), curve.PointG1.Generator().ToAffineCompressed())
element1 := curve.Scalar.Hash([]byte("value1"))
element2 := curve.Scalar.Hash([]byte("value2"))
element3 := curve.Scalar.Hash([]byte("value3"))
elements := []Element{element1, element2, element3}
acc, err = acc.AddElements(key, elements)
require.NoError(t, err)
require.NotEqual(t, acc.value.ToAffineCompressed(), curve.PointG1.Generator().ToAffineCompressed())
}
func TestAccumulatorMarshal(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
point := curve.PointG1.Generator().Mul(curve.Scalar.New(2))
data, err := Accumulator{point}.MarshalBinary()
require.NoError(t, err)
require.NotNil(t, data)
// element cannot be empty
_, err = Accumulator{}.MarshalBinary()
require.Error(t, err)
e := &Accumulator{curve.PointG1.Generator()}
_ = e.UnmarshalBinary(data)
require.True(t, e.value.Equal(point))
}
func TestUpdate(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
var seed [32]byte
key, err := new(SecretKey).New(curve, seed[:])
require.NoError(t, err)
require.NotNil(t, key)
acc, err := new(Accumulator).New(curve)
require.NoError(t, err)
require.NotNil(t, acc)
require.Equal(t, acc.value.ToAffineCompressed(), curve.PointG1.Generator().ToAffineCompressed())
element1 := curve.Scalar.Hash([]byte("value1"))
element2 := curve.Scalar.Hash([]byte("value2"))
element3 := curve.Scalar.Hash([]byte("value3"))
elements := []Element{element1, element2, element3}
acc, _, err = acc.Update(key, elements, nil)
require.NoError(t, err)
require.NotEqual(t, acc.value.ToAffineCompressed(), curve.PointG1.Generator().ToAffineCompressed())
acc, _, err = acc.Update(key, nil, elements)
require.NoError(t, err)
require.Equal(t, acc.value.ToAffineCompressed(), curve.PointG1.Generator().ToAffineCompressed())
}
+244
View File
@@ -0,0 +1,244 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package accumulator
import (
"errors"
"fmt"
"git.sr.ht/~sircmpwn/go-bare"
"github.com/onsonr/sonr/pkg/crypto/core/curves"
)
// SecretKey is the secret alpha only held by the accumulator manager.
type SecretKey struct {
value curves.Scalar
}
// New creates a new secret key from the seed.
func (sk *SecretKey) New(curve *curves.PairingCurve, seed []byte) (*SecretKey, error) {
sk.value = curve.Scalar.Hash(seed)
return sk, nil
}
// GetPublicKey creates a public key from SecretKey sk
func (sk SecretKey) GetPublicKey(curve *curves.PairingCurve) (*PublicKey, error) {
if sk.value == nil || curve == nil {
return nil, fmt.Errorf("curve and sk value cannot be nil")
}
value := curve.Scalar.Point().(curves.PairingPoint).OtherGroup().Generator().Mul(sk.value)
return &PublicKey{value.(curves.PairingPoint)}, nil
}
// MarshalBinary converts SecretKey to bytes
func (sk SecretKey) MarshalBinary() ([]byte, error) {
if sk.value == nil {
return nil, fmt.Errorf("sk cannot be empty")
}
tv := &structMarshal{
Value: sk.value.Bytes(),
Curve: sk.value.Point().CurveName(),
}
return bare.Marshal(tv)
}
// UnmarshalBinary sets SecretKey from bytes
func (sk *SecretKey) UnmarshalBinary(data []byte) error {
tv := new(structMarshal)
err := bare.Unmarshal(data, tv)
if err != nil {
return err
}
curve := curves.GetCurveByName(tv.Curve)
if curve == nil {
return fmt.Errorf("invalid curve")
}
value, err := curve.NewScalar().SetBytes(tv.Value)
if err != nil {
return err
}
sk.value = value
return nil
}
// BatchAdditions computes product(y + sk) for y in additions and output the product
func (sk SecretKey) BatchAdditions(additions []Element) (Element, error) {
if sk.value == nil {
return nil, fmt.Errorf("secret key cannot be empty")
}
mul := sk.value.One()
for i := 0; i < len(additions); i++ {
if additions[i] == nil {
return nil, fmt.Errorf("some element in additions is nil")
}
// y + alpha
temp := additions[i].Add(sk.value)
// prod(y + alpha)
mul = mul.Mul(temp)
}
return mul, nil
}
// BatchDeletions computes 1/product(y + sk) for y in deletions and output it
func (sk SecretKey) BatchDeletions(deletions []Element) (Element, error) {
v, err := sk.BatchAdditions(deletions)
if err != nil {
return nil, err
}
y, err := v.Invert()
if err != nil {
return nil, err
}
return y, nil
}
// CreateCoefficients creates the Batch Polynomial coefficients
// See page 7 of https://eprint.iacr.org/2020/777.pdf
func (sk SecretKey) CreateCoefficients(additions []Element, deletions []Element) ([]Element, error) {
if sk.value == nil {
return nil, fmt.Errorf("secret key should not be nil")
}
// vD(x) = ∑^{m}_{s=1}{ ∏ 1..s {yD_i + alpha}^-1 ∏ 1 ..s-1 {yD_j - x}
one := sk.value.One()
m1 := one.Neg() // m1 is -1
vD := make(polynomial, 0, len(deletions))
for s := 0; s < len(deletions); s++ {
// ∏ 1..s (yD_i + alpha)^-1
c, err := sk.BatchDeletions(deletions[0 : s+1])
if err != nil {
return nil, fmt.Errorf("error in sk batchDeletions")
}
poly := make(polynomial, 1, s+2)
poly[0] = one
// ∏ 1..(s-1) (yD_j - x)
for j := 0; j < s; j++ {
t := make(polynomial, 2)
// yD_j
t[0] = deletions[j]
// -x
t[1] = m1
// polynomial multiplication (yD_1-x) * (yD_2 - x) ...
poly, err = poly.Mul(t)
if err != nil {
return nil, err
}
}
poly, err = poly.MulScalar(c)
if err != nil {
return nil, err
}
vD, err = vD.Add(poly)
if err != nil {
return nil, err
}
}
// vD(x) * ∏ 1..n (yA_i + alpha)
bAdd, err := sk.BatchAdditions(additions)
if err != nil {
return nil, fmt.Errorf("error in sk batchAdditions")
}
vD, err = vD.MulScalar(bAdd)
if err != nil {
return nil, err
}
// vA(x) = ∑^n_{s=1}{ ∏ 1..s-1 {yA_i + alpha} ∏ s+1..n {yA_j - x} }
vA := make(polynomial, 0, len(additions))
for s := 0; s < len(additions); s++ {
// ∏ 1..s-1 {yA_i + alpha}
var c Element
if s == 0 {
c = one
} else {
c, err = sk.BatchAdditions(additions[0:s])
if err != nil {
return nil, err
}
}
poly := make(polynomial, 1, s+2)
poly[0] = one
// ∏ s+1..n {yA_j - x}
for j := s + 1; j < len(additions); j++ {
t := make(polynomial, 2)
t[0] = additions[j]
t[1] = m1
// polynomial multiplication (yA_1-x) * (yA_2 - x) ...
poly, err = poly.Mul(t)
if err != nil {
return nil, err
}
}
poly, err = poly.MulScalar(c)
if err != nil {
return nil, err
}
vA, err = vA.Add(poly)
if err != nil {
return nil, err
}
}
// vA - vD
vA, err = vA.Sub(vD)
if err != nil {
return nil, err
}
result := make([]Element, len(vA))
for i := 0; i < len(vA); i++ {
result[i] = vA[i]
}
return result, nil
}
// PublicKey is the public key of accumulator, it should be sk * generator of G2
type PublicKey struct {
value curves.PairingPoint
}
// MarshalBinary converts PublicKey to bytes
func (pk PublicKey) MarshalBinary() ([]byte, error) {
if pk.value == nil {
return nil, fmt.Errorf("public key cannot be nil")
}
tv := &structMarshal{
Value: pk.value.ToAffineCompressed(),
Curve: pk.value.CurveName(),
}
return bare.Marshal(tv)
}
// UnmarshalBinary sets PublicKey from bytes
func (pk *PublicKey) UnmarshalBinary(data []byte) error {
tv := new(structMarshal)
err := bare.Unmarshal(data, tv)
if err != nil {
return err
}
curve := curves.GetPairingCurveByName(tv.Curve)
if curve == nil {
return fmt.Errorf("invalid curve")
}
value, err := curve.NewScalar().Point().FromAffineCompressed(tv.Value)
if err != nil {
return err
}
var ok bool
pk.value, ok = value.(curves.PairingPoint)
if !ok {
return errors.New("can't convert to PairingPoint")
}
return nil
}
+88
View File
@@ -0,0 +1,88 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package accumulator
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/onsonr/sonr/pkg/crypto/core/curves"
)
func TestSecretKeyMarshal(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
data, err := SecretKey{curve.Scalar.One()}.MarshalBinary()
require.NoError(t, err)
require.NotNil(t, data)
e := &SecretKey{curve.Scalar.New(2)}
err = e.UnmarshalBinary(data)
require.NoError(t, err)
require.Equal(t, e.value.Bytes(), curve.Scalar.One().Bytes())
// element cannot be empty
_, err = SecretKey{}.MarshalBinary()
require.Error(t, err)
}
func TestPublicKeyMarshal(t *testing.T) {
// Actually test both toBytes() and from()
curve := curves.BLS12381(&curves.PointBls12381G1{})
sk := &SecretKey{curve.Scalar.New(3)}
pk, _ := sk.GetPublicKey(curve)
pkBytes, err := pk.MarshalBinary()
require.NoError(t, err)
require.NotNil(t, pkBytes)
pk2 := &PublicKey{}
err = pk2.UnmarshalBinary(pkBytes)
require.NoError(t, err)
require.True(t, pk.value.Equal(pk2.value))
}
func TestBatch(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
var seed [32]byte
sk, _ := new(SecretKey).New(curve, seed[:])
element1 := curve.Scalar.Hash([]byte("value1"))
element2 := curve.Scalar.Hash([]byte("value2"))
elements := []Element{element1, element2}
add, err := sk.BatchAdditions(elements)
require.NoError(t, err)
require.NotNil(t, add)
del, err := sk.BatchDeletions(elements)
require.NoError(t, err)
require.NotNil(t, del)
result := add.Mul(del)
require.Equal(t, result, curve.Scalar.One())
g1 := curve.PointG1.Generator()
acc := g1.Mul(add)
require.NotEqual(t, acc, g1)
acc = acc.Mul(del)
require.Equal(t, acc.ToAffineCompressed(), g1.ToAffineCompressed())
acc2 := g1.Mul(result)
require.True(t, acc2.Equal(g1))
}
func TestCoefficient(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
sk, _ := new(SecretKey).New(curve, []byte("1234567890"))
element1 := curve.Scalar.Hash([]byte("value1"))
element2 := curve.Scalar.Hash([]byte("value2"))
element3 := curve.Scalar.Hash([]byte("value3"))
element4 := curve.Scalar.Hash([]byte("value4"))
element5 := curve.Scalar.Hash([]byte("value5"))
elements := []Element{element1, element2, element3, element4, element5}
coefficients, err := sk.CreateCoefficients(elements[0:2], elements[2:5])
require.NoError(t, err)
require.Equal(t, len(coefficients), 3)
}
+204
View File
@@ -0,0 +1,204 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package accumulator
import (
"fmt"
"math"
"github.com/onsonr/sonr/pkg/crypto/core/curves"
)
// dad constructs two polynomials - dA(x) and dD(x)
// dA(y) = prod(y_A,t - y), t = 1...n
// dD(y) = prod(y_D,t - y), t = 1...n
func dad(values []Element, y Element) (Element, error) {
if values == nil || y == nil {
return nil, fmt.Errorf("curve, values or y should not be nil")
}
for _, value := range values {
if value == nil {
return nil, fmt.Errorf("some element is nil")
}
}
result := y.One()
if len(values) == 1 {
a := values[0]
result = a.Sub(y)
} else {
for i := 0; i < len(values); i++ {
temp := values[i].Sub(y)
result = result.Mul(temp)
}
}
return result, nil
}
type polynomialPoint []curves.Point
// evaluate evaluates a PolynomialG1 on input x.
func (p polynomialPoint) evaluate(x curves.Scalar) (curves.Point, error) {
if p == nil {
return nil, fmt.Errorf("p cannot be empty")
}
for i := 0; i < len(p); i++ {
if p[i] == nil {
return nil, fmt.Errorf("some coefficient in p is nil")
}
}
pp := x
res := p[0]
for i := 1; i < len(p); i++ {
r := p[i].Mul(pp)
res = res.Add(r)
pp = pp.Mul(x)
}
return res, nil
}
// Add adds two PolynomialG1
func (p polynomialPoint) Add(rhs polynomialPoint) (polynomialPoint, error) {
maxLen := int(math.Max(float64(len(p)), float64(len(rhs))))
result := make(polynomialPoint, maxLen)
for i, c := range p {
if c == nil {
return nil, fmt.Errorf("invalid coefficient at %d", i)
}
result[i] = c.Add(c.Identity())
}
for i, c := range rhs {
if c == nil {
return nil, fmt.Errorf("invalid coefficient at %d", i)
}
if result[i] == nil {
result[i] = c.Add(c.Identity())
} else {
result[i] = result[i].Add(c)
}
}
return result, nil
}
// Mul for PolynomialG1 computes rhs * p, p is a polynomial, rhs is a value
func (p polynomialPoint) Mul(rhs curves.Scalar) (polynomialPoint, error) {
result := make(polynomialPoint, len(p))
for i, c := range p {
if c == nil {
return nil, fmt.Errorf("invalid coefficient at %d", i)
}
result[i] = c.Mul(rhs)
}
return result, nil
}
type polynomial []curves.Scalar
// Add adds two polynomials
func (p polynomial) Add(rhs polynomial) (polynomial, error) {
maxLen := int(math.Max(float64(len(p)), float64(len(rhs))))
result := make([]curves.Scalar, maxLen)
for i, c := range p {
if c == nil {
return nil, fmt.Errorf("invalid coefficient at %d", i)
}
result[i] = c.Clone()
}
for i, c := range rhs {
if c == nil {
return nil, fmt.Errorf("invalid coefficient at %d", i)
}
if result[i] == nil {
result[i] = c.Clone()
} else {
result[i] = result[i].Add(c)
}
}
return result, nil
}
// Sub computes p-rhs and returns
func (p polynomial) Sub(rhs polynomial) (polynomial, error) {
maxLen := int(math.Max(float64(len(p)), float64(len(rhs))))
result := make([]curves.Scalar, maxLen)
for i, c := range p {
if c == nil {
return nil, fmt.Errorf("invalid coefficient at %d", i)
}
result[i] = c.Clone()
}
for i, c := range rhs {
if c == nil {
return nil, fmt.Errorf("invalid coefficient at %d", i)
}
if result[i] == nil {
result[i] = c.Neg()
} else {
result[i] = result[i].Sub(c)
}
}
return result, nil
}
// Mul multiplies two polynomials - p * rhs
func (p polynomial) Mul(rhs polynomial) (polynomial, error) {
// Check for each coefficient that should not be nil
for i, c := range p {
if c == nil {
return nil, fmt.Errorf("coefficient in p at %d is nil", i)
}
}
for i, c := range rhs {
if c == nil {
return nil, fmt.Errorf("coefficient in rhs at %d is nil", i)
}
}
m := len(p)
n := len(rhs)
// Initialize the product polynomial
prod := make(polynomial, m+n-1)
for i := 0; i < len(prod); i++ {
prod[i] = p[0].Zero()
}
// Multiply two polynomials term by term
for i, cp := range p {
for j, cr := range rhs {
temp := cp.Mul(cr)
prod[i+j] = prod[i+j].Add(temp)
}
}
return prod, nil
}
// MulScalar computes p * rhs, where rhs is a scalar value
func (p polynomial) MulScalar(rhs curves.Scalar) (polynomial, error) {
result := make(polynomial, len(p))
for i, c := range p {
if c == nil {
return nil, fmt.Errorf("coefficient at %d is nil", i)
}
result[i] = c.Mul(rhs)
}
return result, nil
}
+404
View File
@@ -0,0 +1,404 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package accumulator
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/onsonr/sonr/pkg/crypto/core/curves"
)
func TestEvaluatePolyG1(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
poly := polynomialPoint{
curve.PointG1.Generator().Mul(curve.Scalar.New(3)),
curve.PointG1.Generator().Mul(curve.Scalar.New(2)),
curve.PointG1.Generator().Mul(curve.Scalar.New(1)),
}
output1, err := poly.evaluate(curve.Scalar.New(1))
require.NoError(t, err)
require.NotNil(t, output1)
result1 := curve.PointG1.Generator().Mul(curve.Scalar.New(6))
require.Equal(t, output1.ToAffineCompressed(), result1.ToAffineCompressed())
output2, err := poly.evaluate(curve.Scalar.New(2))
require.NoError(t, err)
require.NotNil(t, output2)
result2 := curve.PointG1.Generator().Mul(curve.Scalar.New(11))
require.Equal(t, output2.ToAffineCompressed(), result2.ToAffineCompressed())
}
func TestEvaluatePolyG1Error(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
poly := polynomialPoint{
nil,
curve.PointG1.Generator().Mul(curve.Scalar.New(2)),
curve.PointG1.Generator().Mul(curve.Scalar.New(1)),
}
_, err := poly.evaluate(curve.Scalar.New(1))
require.Error(t, err)
}
func TestAddAssignPolyG1(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
// Test polynomial with equal length
poly1 := polynomialPoint{
curve.PointG1.Generator().Mul(curve.Scalar.New(3)),
curve.PointG1.Generator().Mul(curve.Scalar.New(2)),
curve.PointG1.Generator().Mul(curve.Scalar.New(1)),
}
poly2 := polynomialPoint{
curve.PointG1.Generator().Mul(curve.Scalar.New(1)),
curve.PointG1.Generator().Mul(curve.Scalar.New(2)),
curve.PointG1.Generator().Mul(curve.Scalar.New(3)),
}
output, err := poly1.Add(poly2)
require.NoError(t, err)
require.NotNil(t, output)
result := polynomialPoint{
curve.PointG1.Generator().Mul(curve.Scalar.New(4)),
curve.PointG1.Generator().Mul(curve.Scalar.New(4)),
curve.PointG1.Generator().Mul(curve.Scalar.New(4)),
}
for i := 0; i < len(output); i++ {
require.Equal(t, output[i].ToAffineCompressed(), result[i].ToAffineCompressed())
}
// Test polynomials with unequal length
poly3 := polynomialPoint{
curve.PointG1.Generator().Mul(curve.Scalar.New(1)),
curve.PointG1.Generator().Mul(curve.Scalar.New(2)),
}
output2, err := poly1.Add(poly3)
require.NoError(t, err)
require.NotNil(t, output2)
result2 := polynomialPoint{
curve.PointG1.Generator().Mul(curve.Scalar.New(4)),
curve.PointG1.Generator().Mul(curve.Scalar.New(4)),
curve.PointG1.Generator().Mul(curve.Scalar.New(1)),
}
require.Equal(t, len(output2), len(result2))
for i := 0; i < len(output2); i++ {
require.Equal(t, output2[i].ToAffineCompressed(), result2[i].ToAffineCompressed())
}
// Test polynomial with Capacity
poly4 := make(polynomialPoint, 0, 3)
poly5, err := poly4.Add(poly1)
require.NoError(t, err)
require.Equal(t, len(poly5), len(poly1))
for i := 0; i < len(poly5); i++ {
require.Equal(t, poly5[i].ToAffineCompressed(), poly1[i].ToAffineCompressed())
}
}
func TestAddAssignPolyG1Error(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
poly1 := polynomialPoint{
nil,
curve.PointG1.Generator().Mul(curve.Scalar.New(2)),
curve.PointG1.Generator().Mul(curve.Scalar.New(1)),
}
poly2 := polynomialPoint{
curve.PointG1.Generator().Mul(curve.Scalar.New(1)),
curve.PointG1.Generator().Mul(curve.Scalar.New(2)),
curve.PointG1.Generator().Mul(curve.Scalar.New(3)),
}
output, err := poly1.Add(poly2)
require.Error(t, err)
require.Nil(t, output)
}
func TestMulAssignPolyG1(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
poly := polynomialPoint{
curve.PointG1.Generator().Mul(curve.Scalar.New(3)),
curve.PointG1.Generator().Mul(curve.Scalar.New(2)),
curve.PointG1.Generator().Mul(curve.Scalar.New(1)),
}
rhs := curve.Scalar.New(3)
output, err := poly.Mul(rhs)
require.NoError(t, err)
require.NotNil(t, output)
poly2 := polynomialPoint{
curve.PointG1.Generator().Mul(curve.Scalar.New(9)),
curve.PointG1.Generator().Mul(curve.Scalar.New(6)),
curve.PointG1.Generator().Mul(curve.Scalar.New(3)),
}
for i := 0; i < len(poly2); i++ {
require.Equal(t, output[i].ToAffineCompressed(), poly2[i].ToAffineCompressed())
}
}
func TestMulAssignPolyG1Error(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
poly := polynomialPoint{
nil,
curve.PointG1.Generator().Mul(curve.Scalar.New(2)),
curve.PointG1.Generator().Mul(curve.Scalar.New(1)),
}
rhs := curve.Scalar.New(3)
output, err := poly.Mul(rhs)
require.Error(t, err)
require.Nil(t, output)
}
func TestPushPoly(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
poly := polynomial{
curve.Scalar.New(3),
curve.Scalar.New(2),
curve.Scalar.New(1),
}
scalar := curve.Scalar.New(4)
result := append(poly, scalar)
require.Equal(t, result[3], scalar)
// Push one more
scalar2 := curve.Scalar.New(5)
result2 := append(result, scalar2)
require.Equal(t, result2[4], scalar2)
// Push to a new polynomial
newPoly := polynomial{}
newPoly = append(newPoly, scalar)
require.Equal(t, newPoly[0], scalar)
newPoly = append(newPoly, scalar2)
require.Equal(t, newPoly[1], scalar2)
}
func TestAddAssignPoly(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
// Test polynomial with equal length
poly1 := polynomial{
curve.Scalar.New(3),
curve.Scalar.New(2),
curve.Scalar.New(1),
}
poly2 := polynomial{
curve.Scalar.New(1),
curve.Scalar.New(2),
curve.Scalar.New(3),
}
output, err := poly1.Add(poly2)
require.NoError(t, err)
require.NotNil(t, output)
result := []curves.Scalar{
curve.Scalar.New(4),
curve.Scalar.New(4),
curve.Scalar.New(4),
}
for i := 0; i < len(output); i++ {
require.Equal(t, output[i], result[i])
}
// Test polynomials with unequal length
poly3 := polynomial{
curve.Scalar.New(1),
curve.Scalar.New(2),
}
output2, err := poly1.Add(poly3)
require.NoError(t, err)
require.NotNil(t, output2)
result2 := []curves.Scalar{
curve.Scalar.New(4),
curve.Scalar.New(4),
curve.Scalar.New(1),
}
require.Equal(t, len(output2), len(result2))
for i := 0; i < len(output2); i++ {
require.Equal(t, output2[i], result2[i])
}
}
func TestAddAssignPolyError(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
// Test polynomial with equal length
poly1 := polynomial{
nil,
curve.Scalar.New(2),
curve.Scalar.New(1),
}
poly2 := polynomial{
curve.Scalar.New(1),
curve.Scalar.New(2),
curve.Scalar.New(3),
}
output, err := poly1.Add(poly2)
require.Error(t, err)
require.Nil(t, output)
}
func TestSubAssignPoly(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
// Test polynomial with equal length
poly1 := polynomial{
curve.Scalar.New(3),
curve.Scalar.New(2),
curve.Scalar.New(1),
}
poly2 := polynomial{
curve.Scalar.New(1),
curve.Scalar.New(2),
curve.Scalar.New(3),
}
output, err := poly1.Sub(poly2)
require.NoError(t, err)
require.NotNil(t, output)
result := []curves.Scalar{
curve.Scalar.New(2),
curve.Scalar.New(0),
curve.Scalar.New(-2),
}
for i := 0; i < len(output); i++ {
require.Equal(t, output[i].Bytes(), result[i].Bytes())
}
// Test polynomials with unequal length
poly3 := polynomial{
curve.Scalar.New(1),
curve.Scalar.New(2),
curve.Scalar.New(3),
curve.Scalar.New(4),
}
output2, err := poly1.Sub(poly3)
require.NoError(t, err)
require.NotNil(t, output2)
result2 := []curves.Scalar{
curve.Scalar.New(2),
curve.Scalar.New(0),
curve.Scalar.New(-2),
curve.Scalar.New(-4),
}
require.Equal(t, len(output2), len(result2))
for i := 0; i < len(output2); i++ {
require.Equal(t, output2[i].Bytes(), result2[i].Bytes())
}
}
func TestSubAssignPolyError(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
poly1 := polynomial{
nil,
curve.Scalar.New(2),
curve.Scalar.New(1),
}
poly2 := polynomial{
curve.Scalar.New(1),
curve.Scalar.New(2),
curve.Scalar.New(3),
}
output, err := poly1.Sub(poly2)
require.Error(t, err)
require.Nil(t, output)
}
func TestMulAssignPoly(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
// Test polynomial with equal length
poly1 := polynomial{
curve.Scalar.New(3),
curve.Scalar.New(2),
curve.Scalar.New(1),
}
poly2 := polynomial{
curve.Scalar.New(1),
curve.Scalar.New(2),
curve.Scalar.New(3),
}
output, err := poly1.Mul(poly2)
require.NoError(t, err)
require.NotNil(t, output)
result := []curves.Scalar{
curve.Scalar.New(3),
curve.Scalar.New(8),
curve.Scalar.New(14),
curve.Scalar.New(8),
curve.Scalar.New(3),
}
for i := 0; i < len(result); i++ {
require.Equal(t, output[i].Bytes(), result[i].Bytes())
}
// Test polynomials with unequal length
poly3 := polynomial{
curve.Scalar.New(1),
curve.Scalar.New(2),
}
output2, err := poly1.Mul(poly3)
require.NoError(t, err)
require.NotNil(t, output2)
result2 := []curves.Scalar{
curve.Scalar.New(3),
curve.Scalar.New(8),
curve.Scalar.New(5),
curve.Scalar.New(2),
}
require.Equal(t, len(output2), 4)
for i := 0; i < len(output2); i++ {
require.Equal(t, output2[i].Bytes(), result2[i].Bytes())
}
}
func TestMulAssignPolyError(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
poly1 := polynomial{
nil,
curve.Scalar.New(2),
curve.Scalar.New(1),
}
poly2 := polynomial{
curve.Scalar.New(1),
curve.Scalar.New(2),
curve.Scalar.New(3),
}
output, err := poly1.Mul(poly2)
require.Error(t, err)
require.Nil(t, output)
}
func TestMulValueAssignPoly(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
poly := polynomial{
curve.Scalar.New(3),
curve.Scalar.New(2),
curve.Scalar.New(1),
}
rhs := curve.Scalar.New(3)
output, err := poly.MulScalar(rhs)
require.NoError(t, err)
require.NotNil(t, output)
coefficients2 := []curves.Scalar{
curve.Scalar.New(9),
curve.Scalar.New(6),
curve.Scalar.New(3),
}
for i := 0; i < len(coefficients2); i++ {
require.Equal(t, output[i].Bytes(), coefficients2[i].Bytes())
}
}
func TestMulValueAssignPolyError(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
poly := polynomial{
nil,
curve.Scalar.New(2),
curve.Scalar.New(1),
}
rhs := curve.Scalar.New(3)
output, err := poly.MulScalar(rhs)
require.Error(t, err)
require.Nil(t, output)
}
+518
View File
@@ -0,0 +1,518 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package accumulator
import (
"bytes"
crand "crypto/rand"
"errors"
"fmt"
"git.sr.ht/~sircmpwn/go-bare"
"github.com/onsonr/sonr/pkg/crypto/core/curves"
)
type proofParamsMarshal struct {
X []byte `bare:"x"`
Y []byte `bare:"y"`
Z []byte `bare:"z"`
Curve string `bare:"curve"`
}
// ProofParams contains four distinct public generators of G1 - X, Y, Z
type ProofParams struct {
x, y, z curves.Point
}
// New samples X, Y, Z, K
func (p *ProofParams) New(curve *curves.PairingCurve, pk *PublicKey, entropy []byte) (*ProofParams, error) {
pkBytes, err := pk.MarshalBinary()
if err != nil {
return nil, err
}
prefix := bytes.Repeat([]byte{0xFF}, 32)
data := append(prefix, entropy...)
data = append(data, pkBytes...)
p.z = curve.Scalar.Point().Hash(data)
data[0] = 0xFE
p.y = curve.Scalar.Point().Hash(data)
data[0] = 0xFD
p.x = curve.Scalar.Point().Hash(data)
return p, nil
}
// MarshalBinary converts ProofParams to bytes
func (p *ProofParams) MarshalBinary() ([]byte, error) {
if p.x == nil || p.y == nil || p.z == nil {
return nil, fmt.Errorf("some value x, y, or z is nil")
}
tv := &proofParamsMarshal{
X: p.x.ToAffineCompressed(),
Y: p.y.ToAffineCompressed(),
Z: p.z.ToAffineCompressed(),
Curve: p.x.CurveName(),
}
return bare.Marshal(tv)
}
// UnmarshalBinary converts bytes to ProofParams
func (p *ProofParams) UnmarshalBinary(data []byte) error {
if data == nil {
return fmt.Errorf("expected non-zero byte sequence")
}
tv := new(proofParamsMarshal)
err := bare.Unmarshal(data, tv)
if err != nil {
return err
}
curve := curves.GetCurveByName(tv.Curve)
if curve == nil {
return fmt.Errorf("invalid curve")
}
x, err := curve.NewIdentityPoint().FromAffineCompressed(tv.X)
if err != nil {
return err
}
y, err := curve.NewIdentityPoint().FromAffineCompressed(tv.Y)
if err != nil {
return err
}
z, err := curve.NewIdentityPoint().FromAffineCompressed(tv.Z)
if err != nil {
return err
}
p.x = x
p.y = y
p.z = z
return nil
}
// MembershipProofCommitting contains value computed in Proof of knowledge and
// Blinding phases as described in section 7 of https://eprint.iacr.org/2020/777.pdf
type MembershipProofCommitting struct {
eC curves.Point
tSigma curves.Point
tRho curves.Point
deltaSigma curves.Scalar
deltaRho curves.Scalar
blindingFactor curves.Scalar
rSigma curves.Scalar
rRho curves.Scalar
rDeltaSigma curves.Scalar
rDeltaRho curves.Scalar
sigma curves.Scalar
rho curves.Scalar
capRSigma curves.Point
capRRho curves.Point
capRDeltaSigma curves.Point
capRDeltaRho curves.Point
capRE curves.Scalar
accumulator curves.Point
witnessValue curves.Scalar
xG1 curves.Point
yG1 curves.Point
zG1 curves.Point
}
// New initiates values of MembershipProofCommitting
func (mpc *MembershipProofCommitting) New(
witness *MembershipWitness,
acc *Accumulator,
pp *ProofParams,
pk *PublicKey,
) (*MembershipProofCommitting, error) {
// Randomly select σ, ρ
sigma := witness.y.Random(crand.Reader)
rho := witness.y.Random(crand.Reader)
// E_C = C + (σ + ρ)Z
t := sigma
t = t.Add(rho)
eC := pp.z
eC = eC.Mul(t)
eC = eC.Add(witness.c)
// T_σ = σX
tSigma := pp.x
tSigma = tSigma.Mul(sigma)
// T_ρ = ρY
tRho := pp.y
tRho = tRho.Mul(rho)
// δ_σ = yσ
deltaSigma := witness.y
deltaSigma = deltaSigma.Mul(sigma)
// δ_ρ = yρ
deltaRho := witness.y
deltaRho = deltaRho.Mul(rho)
// Randomly pick r_σ,r_ρ,r_δσ,r_δρ
rY := witness.y.Random(crand.Reader)
rSigma := witness.y.Random(crand.Reader)
rRho := witness.y.Random(crand.Reader)
rDeltaSigma := witness.y.Random(crand.Reader)
rDeltaRho := witness.y.Random(crand.Reader)
// R_σ = r_σ X
capRSigma := pp.x
capRSigma = capRSigma.Mul(rSigma)
// R_ρ = ρY
capRRho := pp.y
capRRho = capRRho.Mul(rRho)
// R_δσ = r_y T_σ - r_δσ X
negX := pp.x
negX = negX.Neg()
capRDeltaSigma := tSigma.Mul(rY)
capRDeltaSigma = capRDeltaSigma.Add(negX.Mul(rDeltaSigma))
// R_δρ = r_y T_ρ - r_δρ Y
negY := pp.y
negY = negY.Neg()
capRDeltaRho := tRho.Mul(rY)
capRDeltaRho = capRDeltaRho.Add(negY.Mul(rDeltaRho))
// P~
g2 := pk.value.Generator()
// -r_δσ - r_δρ
exp := rDeltaSigma
exp = exp.Add(rDeltaRho)
exp = exp.Neg()
// -r_σ - r_ρ
exp2 := rSigma
exp2 = exp2.Add(rRho)
exp2 = exp2.Neg()
// rY * eC
rYeC := eC.Mul(rY)
// (-r_δσ - r_δρ)*Z
expZ := pp.z.Mul(exp)
// (-r_σ - r_ρ)*Z
exp2Z := pp.z.Mul(exp2)
// Prepare
rYeCPrep, ok := rYeC.(curves.PairingPoint)
if !ok {
return nil, errors.New("incorrect type conversion")
}
g2Prep, ok := g2.(curves.PairingPoint)
if !ok {
return nil, errors.New("incorrect type conversion")
}
expZPrep, ok := expZ.(curves.PairingPoint)
if !ok {
return nil, errors.New("incorrect type conversion")
}
exp2ZPrep, ok := exp2Z.(curves.PairingPoint)
if !ok {
return nil, errors.New("incorrect type conversion")
}
pkPrep := pk.value
// Pairing
capRE := g2Prep.MultiPairing(rYeCPrep, g2Prep, expZPrep, g2Prep, exp2ZPrep, pkPrep)
return &MembershipProofCommitting{
eC,
tSigma,
tRho,
deltaSigma,
deltaRho,
rY,
rSigma,
rRho,
rDeltaSigma,
rDeltaRho,
sigma,
rho,
capRSigma,
capRRho,
capRDeltaSigma,
capRDeltaRho,
capRE,
acc.value,
witness.y,
pp.x,
pp.y,
pp.z,
}, nil
}
// GetChallenge returns bytes that need to be hashed for generating challenge.
// V || Ec || T_sigma || T_rho || R_E || R_sigma || R_rho || R_delta_sigma || R_delta_rho
func (mpc MembershipProofCommitting) GetChallengeBytes() []byte {
res := mpc.accumulator.ToAffineCompressed()
res = append(res, mpc.eC.ToAffineCompressed()...)
res = append(res, mpc.tSigma.ToAffineCompressed()...)
res = append(res, mpc.tRho.ToAffineCompressed()...)
res = append(res, mpc.capRE.Bytes()...)
res = append(res, mpc.capRSigma.ToAffineCompressed()...)
res = append(res, mpc.capRRho.ToAffineCompressed()...)
res = append(res, mpc.capRDeltaSigma.ToAffineCompressed()...)
res = append(res, mpc.capRDeltaRho.ToAffineCompressed()...)
return res
}
// GenProof computes the s values for Fiat-Shamir and return the actual
// proof to be sent to the verifier given the challenge c.
func (mpc *MembershipProofCommitting) GenProof(c curves.Scalar) *MembershipProof {
// s_y = r_y + c*y
sY := schnorr(mpc.blindingFactor, mpc.witnessValue, c)
// s_σ = r_σ + c*σ
sSigma := schnorr(mpc.rSigma, mpc.sigma, c)
// s_ρ = r_ρ + c*ρ
sRho := schnorr(mpc.rRho, mpc.rho, c)
// s_δσ = rδσ + c*δ_σ
sDeltaSigma := schnorr(mpc.rDeltaSigma, mpc.deltaSigma, c)
// s_δρ = rδρ + c*δ_ρ
sDeltaRho := schnorr(mpc.rDeltaRho, mpc.deltaRho, c)
return &MembershipProof{
mpc.eC,
mpc.tSigma,
mpc.tRho,
sSigma,
sRho,
sDeltaSigma,
sDeltaRho,
sY,
}
}
func schnorr(r, v, challenge curves.Scalar) curves.Scalar {
res := v
res = res.Mul(challenge)
res = res.Add(r)
return res
}
type membershipProofMarshal struct {
EC []byte `bare:"e_c"`
TSigma []byte `bare:"t_sigma"`
TRho []byte `bare:"t_rho"`
SSigma []byte `bare:"s_sigma"`
SRho []byte `bare:"s_rho"`
SDeltaSigma []byte `bare:"s_delta_sigma"`
SDeltaRho []byte `bare:"s_delta_rho"`
SY []byte `bare:"s_y"`
Curve string `bare:"curve"`
}
// MembershipProof contains values in the proof to be verified
type MembershipProof struct {
eC curves.Point
tSigma curves.Point
tRho curves.Point
sSigma curves.Scalar
sRho curves.Scalar
sDeltaSigma curves.Scalar
sDeltaRho curves.Scalar
sY curves.Scalar
}
// Finalize computes values in the proof to be verified.
func (mp *MembershipProof) Finalize(acc *Accumulator, pp *ProofParams, pk *PublicKey, challenge curves.Scalar) (*MembershipProofFinal, error) {
// R_σ = s_δ X + c T_σ
negTSigma := mp.tSigma
negTSigma = negTSigma.Neg()
capRSigma := pp.x.Mul(mp.sSigma)
capRSigma = capRSigma.Add(negTSigma.Mul(challenge))
// R_ρ = s_ρ Y + c T_ρ
negTRho := mp.tRho
negTRho = negTRho.Neg()
capRRho := pp.y.Mul(mp.sRho)
capRRho = capRRho.Add(negTRho.Mul(challenge))
// R_δσ = s_y T_σ - s_δσ X
negX := pp.x
negX = negX.Neg()
capRDeltaSigma := mp.tSigma.Mul(mp.sY)
capRDeltaSigma = capRDeltaSigma.Add(negX.Mul(mp.sDeltaSigma))
// R_δρ = s_y T_ρ - s_δρ Y
negY := pp.y
negY = negY.Neg()
capRDeltaRho := mp.tRho.Mul(mp.sY)
capRDeltaRho = capRDeltaRho.Add(negY.Mul(mp.sDeltaRho))
// tildeP
g2 := pk.value.Generator()
// Compute capRE, the pairing
// E_c * s_y
eCsY := mp.eC.Mul(mp.sY)
// (-s_delta_sigma - s_delta_rho) * Z
exp := mp.sDeltaSigma
exp = exp.Add(mp.sDeltaRho)
exp = exp.Neg()
expZ := pp.z.Mul(exp)
// (-c) * V
exp = challenge.Neg()
expV := acc.value.Mul(exp)
// E_c * s_y + (-s_delta_sigma - s_delta_rho) * Z + (-c) * V
lhs := eCsY.Add(expZ).Add(expV)
// (-s_sigma - s_rho) * Z
exp = mp.sSigma
exp = exp.Add(mp.sRho)
exp = exp.Neg()
expZ2 := pp.z.Mul(exp)
// E_c * c
cEc := mp.eC.Mul(challenge)
// (-s_sigma - s_rho) * Z + E_c * c
rhs := cEc.Add(expZ2)
// Prepare
lhsPrep, ok := lhs.(curves.PairingPoint)
if !ok {
return nil, errors.New("incorrect type conversion")
}
g2Prep, ok := g2.(curves.PairingPoint)
if !ok {
return nil, errors.New("incorrect type conversion")
}
rhsPrep, ok := rhs.(curves.PairingPoint)
if !ok {
return nil, errors.New("incorrect type conversion")
}
pkPrep := pk.value
// capRE
capRE := g2Prep.MultiPairing(lhsPrep, g2Prep, rhsPrep, pkPrep)
return &MembershipProofFinal{
acc.value,
mp.eC,
mp.tSigma,
mp.tRho,
capRE,
capRSigma,
capRRho,
capRDeltaSigma,
capRDeltaRho,
}, nil
}
// MarshalBinary converts MembershipProof to bytes
func (mp MembershipProof) MarshalBinary() ([]byte, error) {
tv := &membershipProofMarshal{
EC: mp.eC.ToAffineCompressed(),
TSigma: mp.tSigma.ToAffineCompressed(),
TRho: mp.tRho.ToAffineCompressed(),
SSigma: mp.sSigma.Bytes(),
SRho: mp.sRho.Bytes(),
SDeltaSigma: mp.sDeltaSigma.Bytes(),
SDeltaRho: mp.sDeltaRho.Bytes(),
SY: mp.sY.Bytes(),
Curve: mp.eC.CurveName(),
}
return bare.Marshal(tv)
}
// UnmarshalBinary converts bytes to MembershipProof
func (mp *MembershipProof) UnmarshalBinary(data []byte) error {
if data == nil {
return fmt.Errorf("expected non-zero byte sequence")
}
tv := new(membershipProofMarshal)
err := bare.Unmarshal(data, tv)
if err != nil {
return err
}
curve := curves.GetCurveByName(tv.Curve)
if curve == nil {
return fmt.Errorf("invalid curve")
}
eC, err := curve.NewIdentityPoint().FromAffineCompressed(tv.EC)
if err != nil {
return err
}
tSigma, err := curve.NewIdentityPoint().FromAffineCompressed(tv.TSigma)
if err != nil {
return err
}
tRho, err := curve.NewIdentityPoint().FromAffineCompressed(tv.TRho)
if err != nil {
return err
}
sSigma, err := curve.NewScalar().SetBytes(tv.SSigma)
if err != nil {
return err
}
sRho, err := curve.NewScalar().SetBytes(tv.SRho)
if err != nil {
return err
}
sDeltaSigma, err := curve.NewScalar().SetBytes(tv.SDeltaSigma)
if err != nil {
return err
}
sDeltaRho, err := curve.NewScalar().SetBytes(tv.SDeltaRho)
if err != nil {
return err
}
sY, err := curve.NewScalar().SetBytes(tv.SY)
if err != nil {
return err
}
mp.eC = eC
mp.tSigma = tSigma
mp.tRho = tRho
mp.sSigma = sSigma
mp.sRho = sRho
mp.sDeltaSigma = sDeltaSigma
mp.sDeltaRho = sDeltaRho
mp.sY = sY
return nil
}
// MembershipProofFinal contains values that are input to Fiat-Shamir Heuristic
type MembershipProofFinal struct {
accumulator curves.Point
eC curves.Point
tSigma curves.Point
tRho curves.Point
capRE curves.Scalar
capRSigma curves.Point
capRRho curves.Point
capRDeltaSigma curves.Point
capRDeltaRho curves.Point
}
// GetChallenge computes Fiat-Shamir Heuristic taking input values of MembershipProofFinal
func (m MembershipProofFinal) GetChallenge(curve *curves.PairingCurve) curves.Scalar {
res := m.accumulator.ToAffineCompressed()
res = append(res, m.eC.ToAffineCompressed()...)
res = append(res, m.tSigma.ToAffineCompressed()...)
res = append(res, m.tRho.ToAffineCompressed()...)
res = append(res, m.capRE.Bytes()...)
res = append(res, m.capRSigma.ToAffineCompressed()...)
res = append(res, m.capRRho.ToAffineCompressed()...)
res = append(res, m.capRDeltaSigma.ToAffineCompressed()...)
res = append(res, m.capRDeltaRho.ToAffineCompressed()...)
challenge := curve.Scalar.Hash(res)
return challenge
}
+182
View File
@@ -0,0 +1,182 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package accumulator
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/onsonr/sonr/pkg/crypto/core/curves"
)
func TestProofParamsMarshal(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
sk, _ := new(SecretKey).New(curve, []byte("1234567890"))
pk, _ := sk.GetPublicKey(curve)
params, err := new(ProofParams).New(curve, pk, []byte("entropy"))
require.NoError(t, err)
require.NotNil(t, params.x)
require.NotNil(t, params.y)
require.NotNil(t, params.z)
bytes, err := params.MarshalBinary()
require.NoError(t, err)
require.NotNil(t, bytes)
params2 := &ProofParams{
curve.PointG1.Generator(),
curve.PointG1.Generator(),
curve.PointG1.Generator(),
}
err = params2.UnmarshalBinary(bytes)
require.NoError(t, err)
require.True(t, params.x.Equal(params2.x))
require.True(t, params.y.Equal(params2.y))
require.True(t, params.z.Equal(params2.z))
}
func TestMembershipProof(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
sk, _ := new(SecretKey).New(curve, []byte("1234567890"))
pk, _ := sk.GetPublicKey(curve)
element1 := curve.Scalar.Hash([]byte("3"))
element2 := curve.Scalar.Hash([]byte("4"))
element3 := curve.Scalar.Hash([]byte("5"))
element4 := curve.Scalar.Hash([]byte("6"))
element5 := curve.Scalar.Hash([]byte("7"))
element6 := curve.Scalar.Hash([]byte("8"))
element7 := curve.Scalar.Hash([]byte("9"))
elements := []Element{element1, element2, element3, element4, element5, element6, element7}
// Initiate a new accumulator
acc, err := new(Accumulator).WithElements(curve, sk, elements)
require.NoError(t, err)
require.NotNil(t, acc.value)
// Initiate a new membership witness for value elements[3]
wit, err := new(MembershipWitness).New(elements[3], acc, sk)
require.NoError(t, err)
require.Equal(t, wit.y, elements[3])
// Create proof parameters, which contains randomly sampled G1 points X, Y, Z, K
params, err := new(ProofParams).New(curve, pk, []byte("entropy"))
require.NoError(t, err)
require.NotNil(t, params.x)
require.NotNil(t, params.y)
require.NotNil(t, params.z)
mpc, err := new(MembershipProofCommitting).New(wit, acc, params, pk)
require.NoError(t, err)
testMPC(t, mpc)
challenge := curve.Scalar.Hash(mpc.GetChallengeBytes())
require.NotNil(t, challenge)
proof := mpc.GenProof(challenge)
require.NotNil(t, proof)
testProof(t, proof)
finalProof, err := proof.Finalize(acc, params, pk, challenge)
require.NoError(t, err)
require.NotNil(t, finalProof)
testFinalProof(t, finalProof)
challenge2 := finalProof.GetChallenge(curve)
require.Equal(t, challenge, challenge2)
// Check we can still have a valid proof even if accumulator and witness are updated
data1 := curve.Scalar.Hash([]byte("1"))
data2 := curve.Scalar.Hash([]byte("2"))
data3 := curve.Scalar.Hash([]byte("3"))
data4 := curve.Scalar.Hash([]byte("4"))
data5 := curve.Scalar.Hash([]byte("5"))
data := []Element{data1, data2, data3, data4, data5}
additions := data[0:2]
deletions := data[2:5]
_, coefficients, err := acc.Update(sk, additions, deletions)
require.NoError(t, err)
require.NotNil(t, coefficients)
_, err = wit.BatchUpdate(additions, deletions, coefficients)
require.NoError(t, err)
newParams, err := new(ProofParams).New(curve, pk, []byte("entropy"))
require.NoError(t, err)
require.NotNil(t, newParams.x)
require.NotNil(t, newParams.y)
require.NotNil(t, newParams.z)
newMPC, err := new(MembershipProofCommitting).New(wit, acc, newParams, pk)
require.NoError(t, err)
testMPC(t, newMPC)
challenge3 := curve.Scalar.Hash(newMPC.GetChallengeBytes())
require.NotNil(t, challenge3)
newProof := newMPC.GenProof(challenge3)
require.NotNil(t, newProof)
testProof(t, newProof)
newFinalProof, err := newProof.Finalize(acc, newParams, pk, challenge3)
require.NoError(t, err)
require.NotNil(t, newFinalProof)
testFinalProof(t, newFinalProof)
challenge4 := newFinalProof.GetChallenge(curve)
require.Equal(t, challenge3, challenge4)
}
func testMPC(t *testing.T, mpc *MembershipProofCommitting) {
require.NotNil(t, mpc.eC)
require.NotNil(t, mpc.tSigma)
require.NotNil(t, mpc.tRho)
require.NotNil(t, mpc.deltaSigma)
require.NotNil(t, mpc.deltaRho)
require.NotNil(t, mpc.blindingFactor)
require.NotNil(t, mpc.rSigma)
require.NotNil(t, mpc.rRho)
require.NotNil(t, mpc.rDeltaSigma)
require.NotNil(t, mpc.rDeltaRho)
require.NotNil(t, mpc.sigma)
require.NotNil(t, mpc.rho)
require.NotNil(t, mpc.capRSigma)
require.NotNil(t, mpc.capRRho)
require.NotNil(t, mpc.capRDeltaSigma)
require.NotNil(t, mpc.capRDeltaRho)
require.NotNil(t, mpc.capRE)
require.NotNil(t, mpc.accumulator)
require.NotNil(t, mpc.witnessValue)
require.NotNil(t, mpc.xG1)
require.NotNil(t, mpc.yG1)
require.NotNil(t, mpc.zG1)
}
func testProof(t *testing.T, proof *MembershipProof) {
require.NotNil(t, proof.eC)
require.NotNil(t, proof.tSigma)
require.NotNil(t, proof.tRho)
require.NotNil(t, proof.sSigma)
require.NotNil(t, proof.sRho)
require.NotNil(t, proof.sDeltaSigma)
require.NotNil(t, proof.sDeltaRho)
require.NotNil(t, proof.sY)
}
func testFinalProof(t *testing.T, finalProof *MembershipProofFinal) {
require.NotNil(t, finalProof.accumulator)
require.NotNil(t, finalProof.eC)
require.NotNil(t, finalProof.tSigma)
require.NotNil(t, finalProof.tRho)
require.NotNil(t, finalProof.capRE)
require.NotNil(t, finalProof.capRSigma)
require.NotNil(t, finalProof.capRRho)
require.NotNil(t, finalProof.capRDeltaSigma)
require.NotNil(t, finalProof.capRDeltaRho)
}
+375
View File
@@ -0,0 +1,375 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package accumulator
import (
"errors"
"fmt"
"git.sr.ht/~sircmpwn/go-bare"
"github.com/onsonr/sonr/pkg/crypto/core/curves"
)
// MembershipWitness contains the witness c and the value y respect to the accumulator state.
type MembershipWitness struct {
c curves.Point
y curves.Scalar
}
// New creates a new membership witness
func (mw *MembershipWitness) New(y Element, acc *Accumulator, sk *SecretKey) (*MembershipWitness, error) {
if acc.value == nil || acc.value.IsIdentity() {
return nil, fmt.Errorf("value of accumulator should not be nil")
}
if sk.value == nil || sk.value.IsZero() {
return nil, fmt.Errorf("secret key should not be nil")
}
if y == nil || y.IsZero() {
return nil, fmt.Errorf("y should not be nil")
}
newAcc := &Accumulator{acc.value}
_, err := newAcc.Remove(sk, y)
if err != nil {
return nil, err
}
mw.c = newAcc.value
mw.y = y.Add(y.Zero())
return mw, nil
}
// Verify the MembershipWitness mw is a valid witness as per section 4 in
// <https://eprint.iacr.org/2020/777>
func (mw MembershipWitness) Verify(pk *PublicKey, acc *Accumulator) error {
if mw.c == nil || mw.y == nil || mw.c.IsIdentity() || mw.y.IsZero() {
return fmt.Errorf("c and y should not be nil")
}
if pk.value == nil || pk.value.IsIdentity() {
return fmt.Errorf("invalid public key")
}
if acc.value == nil || acc.value.IsIdentity() {
return fmt.Errorf("accumulator value should not be nil")
}
// Set -tildeP
g2, ok := pk.value.Generator().(curves.PairingPoint)
if !ok {
return errors.New("incorrect type conversion")
}
// y*tildeP + tildeQ, tildeP is a G2 generator.
p, ok := g2.Mul(mw.y).Add(pk.value).(curves.PairingPoint)
if !ok {
return errors.New("incorrect type conversion")
}
// Prepare
witness, ok := mw.c.(curves.PairingPoint)
if !ok {
return errors.New("incorrect type conversion")
}
v, ok := acc.value.Neg().(curves.PairingPoint)
if !ok {
return errors.New("incorrect type conversion")
}
// Check e(witness, y*tildeP + tildeQ) * e(-acc, tildeP) == Identity
result := p.MultiPairing(witness, p, v, g2)
if !result.IsOne() {
return fmt.Errorf("invalid result")
}
return nil
}
// ApplyDelta returns C' = dA(y)/dD(y)*C + 1/dD(y) * <Gamma_y, Omega>
// according to the witness update protocol described in section 4 of
// https://eprint.iacr.org/2020/777.pdf
func (mw *MembershipWitness) ApplyDelta(delta *Delta) (*MembershipWitness, error) {
if mw.c == nil || mw.y == nil || delta == nil {
return nil, fmt.Errorf("y, c or delta should not be nil")
}
// C' = dA(y)/dD(y)*C + 1/dD(y) * <Gamma_y, Omega>
mw.c = mw.c.Mul(delta.d).Add(delta.p)
return mw, nil
}
// BatchUpdate performs batch update as described in section 4
func (mw *MembershipWitness) BatchUpdate(additions []Element, deletions []Element, coefficients []Coefficient) (*MembershipWitness, error) {
delta, err := evaluateDelta(mw.y, additions, deletions, coefficients)
if err != nil {
return nil, err
}
mw, err = mw.ApplyDelta(delta)
if err != nil {
return nil, fmt.Errorf("applyDelta fails")
}
return mw, nil
}
// MultiBatchUpdate performs multi-batch update using epoch as described in section 4.2
func (mw *MembershipWitness) MultiBatchUpdate(A [][]Element, D [][]Element, C [][]Coefficient) (*MembershipWitness, error) {
delta, err := evaluateDeltas(mw.y, A, D, C)
if err != nil {
return nil, fmt.Errorf("evaluateDeltas fails")
}
mw, err = mw.ApplyDelta(delta)
if err != nil {
return nil, err
}
return mw, nil
}
// MarshalBinary converts a membership witness to bytes
func (mw MembershipWitness) MarshalBinary() ([]byte, error) {
if mw.c == nil || mw.y == nil {
return nil, fmt.Errorf("c and y value should not be nil")
}
result := append(mw.c.ToAffineCompressed(), mw.y.Bytes()...)
tv := &structMarshal{
Value: result,
Curve: mw.c.CurveName(),
}
return bare.Marshal(tv)
}
// UnmarshalBinary converts bytes into MembershipWitness
func (mw *MembershipWitness) UnmarshalBinary(data []byte) error {
if data == nil {
return fmt.Errorf("input data should not be nil")
}
tv := new(structMarshal)
err := bare.Unmarshal(data, tv)
if err != nil {
return err
}
curve := curves.GetCurveByName(tv.Curve)
if curve == nil {
return fmt.Errorf("invalid curve")
}
ptLength := len(curve.Point.ToAffineCompressed())
scLength := len(curve.Scalar.Bytes())
expectedLength := ptLength + scLength
if len(tv.Value) != expectedLength {
return fmt.Errorf("invalid byte sequence")
}
cValue, err := curve.Point.FromAffineCompressed(tv.Value[:ptLength])
if err != nil {
return err
}
yValue, err := curve.Scalar.SetBytes(tv.Value[ptLength:])
if err != nil {
return err
}
mw.c = cValue
mw.y = yValue
return nil
}
// Delta contains values d and p, where d should be the division dA(y)/dD(y) on some value y
// p should be equal to 1/dD * <Gamma_y, Omega>
type Delta struct {
d curves.Scalar
p curves.Point
}
// MarshalBinary converts Delta into bytes
func (d *Delta) MarshalBinary() ([]byte, error) {
if d.d == nil || d.p == nil {
return nil, fmt.Errorf("d and p should not be nil")
}
var result []byte
result = append(result, d.p.ToAffineCompressed()...)
result = append(result, d.d.Bytes()...)
tv := &structMarshal{
Value: result,
Curve: d.p.CurveName(),
}
return bare.Marshal(tv)
}
// UnmarshalBinary converts data into Delta
func (d *Delta) UnmarshalBinary(data []byte) error {
if data == nil {
return fmt.Errorf("expected non-zero byte sequence")
}
tv := new(structMarshal)
err := bare.Unmarshal(data, tv)
if err != nil {
return err
}
curve := curves.GetCurveByName(tv.Curve)
if curve == nil {
return fmt.Errorf("invalid curve")
}
ptLength := len(curve.Point.ToAffineCompressed())
scLength := len(curve.Scalar.Bytes())
expectedLength := ptLength + scLength
if len(tv.Value) != expectedLength {
return fmt.Errorf("invalid byte sequence")
}
pValue, err := curve.NewIdentityPoint().FromAffineCompressed(tv.Value[:ptLength])
if err != nil {
return err
}
dValue, err := curve.NewScalar().SetBytes(tv.Value[ptLength:])
if err != nil {
return err
}
d.d = dValue
d.p = pValue
return nil
}
// evaluateDeltas compute values used for membership witness batch update with epoch
// as described in section 4.2, page 11 of https://eprint.iacr.org/2020/777.pdf
func evaluateDeltas(y Element, A [][]Element, D [][]Element, C [][]Coefficient) (*Delta, error) {
if len(A) != len(D) || len(A) != len(C) {
return nil, fmt.Errorf("a, d, c should have same length")
}
one := y.One()
size := len(A)
// dA(x) = ∏ 1..n (yA_i - x)
aa := make([]curves.Scalar, 0)
// dD(x) = ∏ 1..m (yD_i - x)
dd := make([]curves.Scalar, 0)
a := one
d := one
// dA_{a->b}(y) = ∏ a..b dAs(y)
// dD_{a->b}(y) = ∏ a..b dDs(y)
for i := 0; i < size; i++ {
adds := A[i]
dels := D[i]
// ta = dAs(y)
ta, err := dad(adds, y)
if err != nil {
return nil, fmt.Errorf("dad on additions fails")
}
// td = dDs(y)
td, err := dad(dels, y)
if err != nil {
return nil, fmt.Errorf("dad on deletions fails")
}
// ∏ a..b dAs(y)
a = a.Mul(ta)
// ∏ a..b dDs(y)
d = d.Mul(td)
aa = append(aa, ta)
dd = append(dd, td)
}
// If this fails, then this value was removed.
d, err := d.Invert()
if err != nil {
return nil, fmt.Errorf("no inverse exists")
}
// <Gamma_y, Omega>
p := make(polynomialPoint, 0, size)
// Ωi->j+1 = ∑ 1..t (dAt * dDt-1) · Ω
for i := 0; i < size; i++ {
// t = i+1
// ∏^(t-1)_(h=i+1)
ddh := one
// dDi→t1 (y)
for h := 0; h < i; h++ {
ddh = ddh.Mul(dd[h])
}
// ∏^(j+1)_(k=t+1)
dak := one
// dAt->j(y)
for k := i + 1; k < size; k++ {
dak = dak.Mul(aa[k])
}
// dDi->t-1(y) * dAt->j(y)
dak = dak.Mul(ddh)
pp := make(polynomialPoint, len(C[i]))
for j := 0; j < len(pp); j++ {
pp[j] = C[i][j]
}
// dDi->t-1(y) * dAt->j(y) · Ω
pp, err := pp.Mul(dak)
if err != nil {
return nil, fmt.Errorf("pp.Mul fails")
}
p, err = p.Add(pp)
if err != nil {
return nil, fmt.Errorf("pp.Add fails")
}
}
// dAi->j(y)/dDi->j(y)
a = a.Mul(d)
// Ωi->j(y)
v, err := p.evaluate(y)
if err != nil {
return nil, fmt.Errorf("p.evaluate fails")
}
// (1/dDi->j(y)) * Ωi->j(y)
v = v.Mul(d)
// return
return &Delta{d: a, p: v}, nil
}
// evaluateDelta computes values used for membership witness batch update
// as described in section 4.1 of https://eprint.iacr.org/2020/777.pdf
func evaluateDelta(y Element, additions []Element, deletions []Element, coefficients []Coefficient) (*Delta, error) {
// dD(y) = ∏ 1..m (yD_i - y), d = 1/dD(y)
var err error
d, err := dad(deletions, y)
if err != nil {
return nil, fmt.Errorf("dad fails on deletions")
}
d, err = d.Invert()
if err != nil {
return nil, fmt.Errorf("no inverse exists")
}
// dA(y) = ∏ 1..n (yA_i - y)
a, err := dad(additions, y)
if err != nil {
return nil, fmt.Errorf("dad fails on additions")
}
// dA(y)/dD(y)
a = a.Mul(d)
// Create a PolynomialG1 from coefficients
p := make(polynomialPoint, len(coefficients))
for i := 0; i < len(coefficients); i++ {
p[i] = coefficients[i]
}
// <Gamma_y, Omega>
v, err := p.evaluate(y)
if err != nil {
return nil, fmt.Errorf("p.evaluate fails")
}
// 1/dD * <Gamma_y, Omega>
v = v.Mul(d)
return &Delta{d: a, p: v}, nil
}
+229
View File
@@ -0,0 +1,229 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package accumulator
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/onsonr/sonr/pkg/crypto/core/curves"
)
func Test_Membership_Witness_New(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
var seed [32]byte
key, _ := new(SecretKey).New(curve, seed[:])
acc, _ := new(Accumulator).New(curve)
e := curve.Scalar.New(2)
mw, err := new(MembershipWitness).New(e, acc, key)
require.NoError(t, err)
require.NotNil(t, mw.c)
require.NotNil(t, mw.y)
}
func Test_Membership_Witness_Marshal(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
mw := &MembershipWitness{
curve.PointG1.Generator().Mul(curve.Scalar.New(10)),
curve.Scalar.New(15),
}
data, err := mw.MarshalBinary()
require.NoError(t, err)
require.NotNil(t, data)
newMW := &MembershipWitness{}
err = newMW.UnmarshalBinary(data)
require.NoError(t, err)
require.True(t, mw.c.Equal(newMW.c))
require.Equal(t, 0, mw.y.Cmp(newMW.y))
}
func Test_Membership(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
sk, _ := new(SecretKey).New(curve, []byte("1234567890"))
pk, _ := sk.GetPublicKey(curve)
element1 := curve.Scalar.Hash([]byte("3"))
element2 := curve.Scalar.Hash([]byte("4"))
element3 := curve.Scalar.Hash([]byte("5"))
element4 := curve.Scalar.Hash([]byte("6"))
element5 := curve.Scalar.Hash([]byte("7"))
element6 := curve.Scalar.Hash([]byte("8"))
element7 := curve.Scalar.Hash([]byte("9"))
elements := []Element{element1, element2, element3, element4, element5, element6, element7}
// nm_witness_max works as well if set to value larger than 0 for this test.x
acc, err := new(Accumulator).WithElements(curve, sk, elements)
require.NoError(t, err)
require.NotNil(t, acc.value)
require.False(t, acc.value.IsIdentity())
require.True(t, acc.value.IsOnCurve())
require.NotEqual(t, acc.value, curve.NewG1GeneratorPoint())
wit, err := new(MembershipWitness).New(elements[3], acc, sk)
require.NoError(t, err)
require.Equal(t, wit.y, elements[3])
err = wit.Verify(pk, acc)
require.NoError(t, err)
// Test wrong cases, forge a wrong witness
wrongWit := MembershipWitness{
curve.PointG1.Identity(),
curve.Scalar.One(),
}
err = wrongWit.Verify(pk, acc)
require.Error(t, err)
// Test wrong cases, forge a wrong accumulator
wrongAcc := &Accumulator{
curve.PointG1.Generator(),
}
err = wit.Verify(pk, wrongAcc)
require.Error(t, err)
}
func Test_Membership_Batch_Update(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
sk, _ := new(SecretKey).New(curve, []byte("1234567890"))
pk, _ := sk.GetPublicKey(curve)
element1 := curve.Scalar.Hash([]byte("3"))
element2 := curve.Scalar.Hash([]byte("4"))
element3 := curve.Scalar.Hash([]byte("5"))
element4 := curve.Scalar.Hash([]byte("6"))
element5 := curve.Scalar.Hash([]byte("7"))
element6 := curve.Scalar.Hash([]byte("8"))
element7 := curve.Scalar.Hash([]byte("9"))
elements := []Element{element1, element2, element3, element4, element5, element6, element7}
// nm_witness_max works as well if set to value larger than 0 for this test.
acc, err := new(Accumulator).WithElements(curve, sk, elements)
require.NoError(t, err)
require.NotNil(t, acc.value)
wit, err := new(MembershipWitness).New(elements[3], acc, sk)
require.NoError(t, err)
require.Equal(t, wit.y, elements[3])
err = wit.Verify(pk, acc)
require.Nil(t, err)
data1 := curve.Scalar.Hash([]byte("1"))
data2 := curve.Scalar.Hash([]byte("2"))
data3 := curve.Scalar.Hash([]byte("3"))
data4 := curve.Scalar.Hash([]byte("4"))
data5 := curve.Scalar.Hash([]byte("5"))
data := []Element{data1, data2, data3, data4, data5}
additions := data[0:2]
deletions := data[2:5]
_, coefficients, err := acc.Update(sk, additions, deletions)
require.NoError(t, err)
require.NotNil(t, coefficients)
_, err = wit.BatchUpdate(additions, deletions, coefficients)
require.NoError(t, err)
err = wit.Verify(pk, acc)
require.Nil(t, err)
}
func Test_Membership_Multi_Batch_Update(t *testing.T) {
curve := curves.BLS12381(&curves.PointBls12381G1{})
sk, _ := new(SecretKey).New(curve, []byte("1234567890"))
pk, _ := sk.GetPublicKey(curve)
element1 := curve.Scalar.Hash([]byte("3"))
element2 := curve.Scalar.Hash([]byte("4"))
element3 := curve.Scalar.Hash([]byte("5"))
element4 := curve.Scalar.Hash([]byte("6"))
element5 := curve.Scalar.Hash([]byte("7"))
element6 := curve.Scalar.Hash([]byte("8"))
element7 := curve.Scalar.Hash([]byte("9"))
element8 := curve.Scalar.Hash([]byte("10"))
element9 := curve.Scalar.Hash([]byte("11"))
element10 := curve.Scalar.Hash([]byte("12"))
element11 := curve.Scalar.Hash([]byte("13"))
element12 := curve.Scalar.Hash([]byte("14"))
element13 := curve.Scalar.Hash([]byte("15"))
element14 := curve.Scalar.Hash([]byte("16"))
element15 := curve.Scalar.Hash([]byte("17"))
element16 := curve.Scalar.Hash([]byte("18"))
element17 := curve.Scalar.Hash([]byte("19"))
element18 := curve.Scalar.Hash([]byte("20"))
elements := []Element{
element1,
element2,
element3,
element4,
element5,
element6,
element7,
element8,
element9,
element10,
element11,
element12,
element13,
element14,
element15,
element16,
element17,
element18,
}
acc, err := new(Accumulator).WithElements(curve, sk, elements)
require.NoError(t, err)
require.NotNil(t, acc.value)
wit, err := new(MembershipWitness).New(elements[3], acc, sk)
require.NoError(t, err)
err = wit.Verify(pk, acc)
require.Nil(t, err)
data1 := curve.Scalar.Hash([]byte("1"))
data2 := curve.Scalar.Hash([]byte("2"))
data3 := curve.Scalar.Hash([]byte("3"))
data4 := curve.Scalar.Hash([]byte("4"))
data5 := curve.Scalar.Hash([]byte("5"))
data := []Element{data1, data2, data3, data4, data5}
adds1 := data[0:2]
dels1 := data[2:5]
_, coeffs1, err := acc.Update(sk, adds1, dels1)
require.NoError(t, err)
require.NotNil(t, coeffs1)
dels2 := elements[8:10]
_, coeffs2, err := acc.Update(sk, []Element{}, dels2)
require.NoError(t, err)
require.NotNil(t, coeffs2)
dels3 := elements[11:14]
_, coeffs3, err := acc.Update(sk, []Element{}, dels3)
require.NoError(t, err)
require.NotNil(t, coeffs3)
a := make([][]Element, 3)
a[0] = adds1
a[1] = []Element{}
a[2] = []Element{}
d := make([][]Element, 3)
d[0] = dels1
d[1] = dels2
d[2] = dels3
c := make([][]Coefficient, 3)
c[0] = coeffs1
c[1] = coeffs2
c[2] = coeffs3
_, err = wit.MultiBatchUpdate(a, d, c)
require.NoError(t, err)
err = wit.Verify(pk, acc)
require.Nil(t, err)
}
+66
View File
@@ -0,0 +1,66 @@
package bip32
import (
"crypto/hmac"
"crypto/sha512"
"encoding/binary"
"errors"
"math/big"
"github.com/btcsuite/btcd/btcec/v2"
)
// ComputePublicKey computes the public key of a child key given the extended public key, chain code, and index.
func ComputePublicKey(extPubKey []byte, chainCode uint32, index int) ([]byte, error) {
// Check if the index is a hardened child key
if chainCode&0x80000000 != 0 && index < 0 {
return nil, errors.New("invalid index")
}
// Serialize the public key
pubKey, err := btcec.ParsePubKey(extPubKey)
if err != nil {
return nil, err
}
pubKeyBytes := pubKey.SerializeCompressed()
// Serialize the index
indexBytes := make([]byte, 4)
binary.BigEndian.PutUint32(indexBytes, uint32(index))
// Compute the HMAC-SHA512
mac := hmac.New(sha512.New, []byte{byte(chainCode)})
mac.Write(pubKeyBytes)
mac.Write(indexBytes)
I := mac.Sum(nil)
// Split I into two 32-byte sequences
IL := I[:32]
// Convert IL to a big integer
ilNum := new(big.Int).SetBytes(IL)
// Check if parse256(IL) >= n
curve := btcec.S256()
if ilNum.Cmp(curve.N) >= 0 {
return nil, errors.New("invalid child key")
}
// Compute the child public key
ilx, ily := curve.ScalarBaseMult(IL)
childX, childY := curve.Add(ilx, ily, pubKey.X(), pubKey.Y())
lx := newBigIntFieldVal(childX)
ly := newBigIntFieldVal(childY)
// Create the child public key
childPubKey := btcec.NewPublicKey(lx, ly)
childPubKeyBytes := childPubKey.SerializeCompressed()
return childPubKeyBytes, nil
}
// newBigIntFieldVal creates a new field value from a big integer.
func newBigIntFieldVal(val *big.Int) *btcec.FieldVal {
lx := new(btcec.FieldVal)
lx.SetByteSlice(val.Bytes())
return lx
}
+57
View File
@@ -0,0 +1,57 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package bulletproof
import (
"github.com/pkg/errors"
"golang.org/x/crypto/sha3"
"github.com/onsonr/sonr/pkg/crypto/core/curves"
)
// generators contains a list of points to be used as generators for bulletproofs.
type generators []curves.Point
// ippGenerators holds generators necessary for an Inner Product Proof
// It includes a single u generator, and a list of generators divided in half to G and H
// See lines 10 on pg 16 of https://eprint.iacr.org/2017/1066.pdf
type ippGenerators struct {
G generators
H generators
}
// getGeneratorPoints generates generators using HashToCurve with Shake256(domain) as input
// lenVector is the length of the scalars used for the Inner Product Proof
// getGeneratorPoints will return 2*lenVector + 1 total points, split between a single u generator
// and G and H lists of vectors per the IPP specification
// See lines 10 on pg 16 of https://eprint.iacr.org/2017/1066.pdf
func getGeneratorPoints(lenVector int, domain []byte, curve curves.Curve) (*ippGenerators, error) {
shake := sha3.NewShake256()
_, err := shake.Write(domain)
if err != nil {
return nil, errors.Wrap(err, "getGeneratorPoints shake.Write")
}
numPoints := lenVector * 2
points := make([]curves.Point, numPoints)
for i := 0; i < numPoints; i++ {
bytes := [64]byte{}
_, err := shake.Read(bytes[:])
if err != nil {
return nil, errors.Wrap(err, "getGeneratorPoints shake.Read")
}
nextPoint := curve.Point.Hash(bytes[:])
points[i] = nextPoint
}
// Get G and H by splitting points in half
G, H, err := splitPointVector(points)
if err != nil {
return nil, errors.Wrap(err, "getGeneratorPoints splitPointVector")
}
out := ippGenerators{G: G, H: H}
return &out, nil
}
+61
View File
@@ -0,0 +1,61 @@
package bulletproof
import (
"testing"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/sha3"
"github.com/onsonr/sonr/pkg/crypto/core/curves"
)
func TestGeneratorsHappyPath(t *testing.T) {
curve := curves.ED25519()
gs, err := getGeneratorPoints(10, []byte("test"), *curve)
gsConcatenated := concatIPPGenerators(*gs)
require.NoError(t, err)
require.Len(t, gs.G, 10)
require.Len(t, gs.H, 10)
require.True(t, noDuplicates(gsConcatenated))
}
func TestGeneratorsUniquePerDomain(t *testing.T) {
curve := curves.ED25519()
gs1, err := getGeneratorPoints(10, []byte("test"), *curve)
gs1Concatenated := concatIPPGenerators(*gs1)
require.NoError(t, err)
gs2, err := getGeneratorPoints(10, []byte("test2"), *curve)
gs2Concatenated := concatIPPGenerators(*gs2)
require.NoError(t, err)
require.True(t, areDisjoint(gs1Concatenated, gs2Concatenated))
}
func noDuplicates(gs generators) bool {
seen := map[[32]byte]bool{}
for _, G := range gs {
value := sha3.Sum256(G.ToAffineCompressed())
if seen[value] {
return false
}
seen[value] = true
}
return true
}
func areDisjoint(gs1, gs2 generators) bool {
for _, g1 := range gs1 {
for _, g2 := range gs2 {
if g1.Equal(g2) {
return false
}
}
}
return true
}
func concatIPPGenerators(ippGens ippGenerators) generators {
var out generators
out = append(out, ippGens.G...)
out = append(out, ippGens.H...)
return out
}
+181
View File
@@ -0,0 +1,181 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package bulletproof
import (
"github.com/pkg/errors"
"github.com/onsonr/sonr/pkg/crypto/core/curves"
)
// innerProduct takes two lists of scalars (a, b) and performs the dot product returning a single scalar.
func innerProduct(a, b []curves.Scalar) (curves.Scalar, error) {
if len(a) != len(b) {
return nil, errors.New("length of scalar vectors must be the same")
}
if len(a) < 1 {
return nil, errors.New("length of vectors must be at least one")
}
// Get a new scalar of value zero of the same curve as input arguments
innerProduct := a[0].Zero()
for i, aElem := range a {
bElem := b[i]
// innerProduct = aElem*bElem + innerProduct
innerProduct = aElem.MulAdd(bElem, innerProduct)
}
return innerProduct, nil
}
// splitPointVector takes a vector of points, splits it in half returning each half.
func splitPointVector(points []curves.Point) ([]curves.Point, []curves.Point, error) {
if len(points) < 1 {
return nil, nil, errors.New("length of points must be at least one")
}
if len(points)&0x01 != 0 {
return nil, nil, errors.New("length of points must be even")
}
nPrime := len(points) >> 1
firstHalf := points[:nPrime]
secondHalf := points[nPrime:]
return firstHalf, secondHalf, nil
}
// splitScalarVector takes a vector of scalars, splits it in half returning each half.
func splitScalarVector(scalars []curves.Scalar) ([]curves.Scalar, []curves.Scalar, error) {
if len(scalars) < 1 {
return nil, nil, errors.New("length of scalars must be at least one")
}
if len(scalars)&0x01 != 0 {
return nil, nil, errors.New("length of scalars must be even")
}
nPrime := len(scalars) >> 1
firstHalf := scalars[:nPrime]
secondHalf := scalars[nPrime:]
return firstHalf, secondHalf, nil
}
// multiplyScalarToPointVector takes a single scalar and a list of points, multiplies each point by scalar.
func multiplyScalarToPointVector(x curves.Scalar, g []curves.Point) []curves.Point {
products := make([]curves.Point, len(g))
for i, gElem := range g {
product := gElem.Mul(x)
products[i] = product
}
return products
}
// multiplyScalarToScalarVector takes a single scalar (x) and a list of scalars (a), multiplies each scalar in the vector by the scalar.
func multiplyScalarToScalarVector(x curves.Scalar, a []curves.Scalar) []curves.Scalar {
products := make([]curves.Scalar, len(a))
for i, aElem := range a {
product := aElem.Mul(x)
products[i] = product
}
return products
}
// multiplyPairwisePointVectors takes two lists of points (g, h) and performs a pairwise multiplication returning a list of points.
func multiplyPairwisePointVectors(g, h []curves.Point) ([]curves.Point, error) {
if len(g) != len(h) {
return nil, errors.New("length of point vectors must be the same")
}
product := make([]curves.Point, len(g))
for i, gElem := range g {
product[i] = gElem.Add(h[i])
}
return product, nil
}
// multiplyPairwiseScalarVectors takes two lists of points (a, b) and performs a pairwise multiplication returning a list of scalars.
func multiplyPairwiseScalarVectors(a, b []curves.Scalar) ([]curves.Scalar, error) {
if len(a) != len(b) {
return nil, errors.New("length of point vectors must be the same")
}
product := make([]curves.Scalar, len(a))
for i, aElem := range a {
product[i] = aElem.Mul(b[i])
}
return product, nil
}
// addPairwiseScalarVectors takes two lists of scalars (a, b) and performs a pairwise addition returning a list of scalars.
func addPairwiseScalarVectors(a, b []curves.Scalar) ([]curves.Scalar, error) {
if len(a) != len(b) {
return nil, errors.New("length of scalar vectors must be the same")
}
sum := make([]curves.Scalar, len(a))
for i, aElem := range a {
sum[i] = aElem.Add(b[i])
}
return sum, nil
}
// subtractPairwiseScalarVectors takes two lists of scalars (a, b) and performs a pairwise subtraction returning a list of scalars.
func subtractPairwiseScalarVectors(a, b []curves.Scalar) ([]curves.Scalar, error) {
if len(a) != len(b) {
return nil, errors.New("length of scalar vectors must be the same")
}
diff := make([]curves.Scalar, len(a))
for i, aElem := range a {
diff[i] = aElem.Sub(b[i])
}
return diff, nil
}
// invertScalars takes a list of scalars then returns a list with each element inverted.
func invertScalars(xs []curves.Scalar) ([]curves.Scalar, error) {
xinvs := make([]curves.Scalar, len(xs))
for i, x := range xs {
xinv, err := x.Invert()
if err != nil {
return nil, errors.Wrap(err, "bulletproof helpers invertx")
}
xinvs[i] = xinv
}
return xinvs, nil
}
// isPowerOfTwo returns whether a number i is a power of two or not.
func isPowerOfTwo(i int) bool {
return i&(i-1) == 0
}
// get2nVector returns a scalar vector 2^n such that [1, 2, 4, ... 2^(n-1)]
// See k^n and 2^n definitions on pg 12 of https://eprint.iacr.org/2017/1066.pdf
func get2nVector(length int, curve curves.Curve) []curves.Scalar {
vector2n := make([]curves.Scalar, length)
vector2n[0] = curve.Scalar.One()
for i := 1; i < length; i++ {
vector2n[i] = vector2n[i-1].Double()
}
return vector2n
}
func get1nVector(length int, curve curves.Curve) []curves.Scalar {
vector1n := make([]curves.Scalar, length)
for i := 0; i < length; i++ {
vector1n[i] = curve.Scalar.One()
}
return vector1n
}
func getknVector(k curves.Scalar, length int, curve curves.Curve) []curves.Scalar {
vectorkn := make([]curves.Scalar, length)
vectorkn[0] = curve.Scalar.One()
vectorkn[1] = k
for i := 2; i < length; i++ {
vectorkn[i] = vectorkn[i-1].Mul(k)
}
return vectorkn
}
+85
View File
@@ -0,0 +1,85 @@
package bulletproof
import (
crand "crypto/rand"
"testing"
"github.com/stretchr/testify/require"
"github.com/onsonr/sonr/pkg/crypto/core/curves"
)
func TestInnerProductHappyPath(t *testing.T) {
curve := curves.ED25519()
a := randScalarVec(3, *curve)
b := randScalarVec(3, *curve)
_, err := innerProduct(a, b)
require.NoError(t, err)
}
func TestInnerProductMismatchedLengths(t *testing.T) {
curve := curves.ED25519()
a := randScalarVec(3, *curve)
b := randScalarVec(4, *curve)
_, err := innerProduct(a, b)
require.Error(t, err)
}
func TestInnerProductEmptyVector(t *testing.T) {
curve := curves.ED25519()
a := randScalarVec(0, *curve)
b := randScalarVec(0, *curve)
_, err := innerProduct(a, b)
require.Error(t, err)
}
func TestInnerProductOut(t *testing.T) {
curve := curves.ED25519()
a := randScalarVec(2, *curve)
b := randScalarVec(2, *curve)
c, err := innerProduct(a, b)
require.NoError(t, err)
// Calculate manually a0*b0 + a1*b1
cPrime := a[0].Mul(b[0]).Add(a[1].Mul(b[1]))
require.Equal(t, c, cPrime)
}
func TestSplitListofPointsHappyPath(t *testing.T) {
curve := curves.ED25519()
points := randPointVec(10, *curve)
firstHalf, secondHalf, err := splitPointVector(points)
require.NoError(t, err)
require.Len(t, firstHalf, 5)
require.Len(t, secondHalf, 5)
}
func TestSplitListofPointsOddLength(t *testing.T) {
curve := curves.ED25519()
points := randPointVec(11, *curve)
_, _, err := splitPointVector(points)
require.Error(t, err)
}
func TestSplitListofPointsZeroLength(t *testing.T) {
curve := curves.ED25519()
points := randPointVec(0, *curve)
_, _, err := splitPointVector(points)
require.Error(t, err)
}
func randScalarVec(length int, curve curves.Curve) []curves.Scalar {
out := make([]curves.Scalar, length)
for i := 0; i < length; i++ {
out[i] = curve.Scalar.Random(crand.Reader)
}
return out
}
func randPointVec(length int, curve curves.Curve) []curves.Point {
out := make([]curves.Point, length)
for i := 0; i < length; i++ {
out[i] = curve.Point.Random(crand.Reader)
}
return out
}
+396
View File
@@ -0,0 +1,396 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
// Package bulletproof implements the zero knowledge protocol bulletproofs as defined in https://eprint.iacr.org/2017/1066.pdf
package bulletproof
import (
"github.com/gtank/merlin"
"github.com/pkg/errors"
"github.com/onsonr/sonr/pkg/crypto/core/curves"
)
// InnerProductProver is the struct used to create InnerProductProofs
// It specifies which curve to use and holds precomputed generators
// See NewInnerProductProver() for prover initialization.
type InnerProductProver struct {
curve curves.Curve
generators ippGenerators
}
// InnerProductProof contains necessary output for the inner product proof
// a and b are the final input vectors of scalars, they should be of length 1
// Ls and Rs are calculated per recursion of the IPP and are necessary for verification
// See section 3.1 on pg 15 of https://eprint.iacr.org/2017/1066.pdf
type InnerProductProof struct {
a, b curves.Scalar
capLs, capRs []curves.Point
curve *curves.Curve
}
// ippRecursion is the same as IPP but tracks recursive a', b', g', h' and Ls and Rs
// It should only be used internally by InnerProductProver.Prove()
// See L35 on pg 16 of https://eprint.iacr.org/2017/1066.pdf
type ippRecursion struct {
a, b []curves.Scalar
c curves.Scalar
capLs, capRs []curves.Point
g, h []curves.Point
u, capP curves.Point
transcript *merlin.Transcript
}
// NewInnerProductProver initializes a new prover
// It uses the specified domain to generate generators for vectors of at most maxVectorLength
// A prover can be used to construct inner product proofs for vectors of length less than or equal to maxVectorLength
// A prover is defined by an explicit curve.
func NewInnerProductProver(maxVectorLength int, domain []byte, curve curves.Curve) (*InnerProductProver, error) {
generators, err := getGeneratorPoints(maxVectorLength, domain, curve)
if err != nil {
return nil, errors.Wrap(err, "ipp getGenerators")
}
return &InnerProductProver{curve: curve, generators: *generators}, nil
}
// NewInnerProductProof initializes a new InnerProductProof for a specified curve
// This should be used in tandem with UnmarshalBinary() to convert a marshaled proof into the struct.
func NewInnerProductProof(curve *curves.Curve) *InnerProductProof {
var capLs, capRs []curves.Point
newProof := InnerProductProof{
a: curve.NewScalar(),
b: curve.NewScalar(),
capLs: capLs,
capRs: capRs,
curve: curve,
}
return &newProof
}
// rangeToIPP takes the output of a range proof and converts it into an inner product proof
// See section 4.2 on pg 20
// The conversion specifies generators to use (g and hPrime), as well as the two vectors l, r of which the inner product is tHat
// Additionally, note that the P used for the IPP is in fact P*h^-mu from the range proof.
func (prover *InnerProductProver) rangeToIPP(proofG, proofH []curves.Point, l, r []curves.Scalar, tHat curves.Scalar, capPhmuinv, u curves.Point, transcript *merlin.Transcript) (*InnerProductProof, error) {
// Note that P as a witness is only g^l * h^r
// P needs to be in the form of g^l * h^r * u^<l,r>
// Calculate the final P including the u^<l,r> term
utHat := u.Mul(tHat)
capP := capPhmuinv.Add(utHat)
// Use params to prove inner product
recursionParams := &ippRecursion{
a: l,
b: r,
capLs: []curves.Point{},
capRs: []curves.Point{},
c: tHat,
g: proofG,
h: proofH,
capP: capP,
u: u,
transcript: transcript,
}
return prover.proveRecursive(recursionParams)
}
// getP returns the initial P value given two scalars a,b and point u
// This method should only be used for testing
// See (3) on page 13 of https://eprint.iacr.org/2017/1066.pdf
func (prover *InnerProductProver) getP(a, b []curves.Scalar, u curves.Point) (curves.Point, error) {
// Vectors must have length power of two
if !isPowerOfTwo(len(a)) {
return nil, errors.New("ipp vector length must be power of two")
}
// Generator vectors must be same length
if len(prover.generators.G) != len(prover.generators.H) {
return nil, errors.New("ipp generator lengths of g and h must be equal")
}
// Inner product requires len(a) == len(b) else error is returned
c, err := innerProduct(a, b)
if err != nil {
return nil, errors.Wrap(err, "ipp getInnerProduct")
}
// In case where len(a) is less than number of generators precomputed by prover, trim to length
proofG := prover.generators.G[0:len(a)]
proofH := prover.generators.H[0:len(b)]
// initial P = g^a * h^b * u^(a dot b) (See (3) on page 13 of https://eprint.iacr.org/2017/1066.pdf)
ga := prover.curve.NewGeneratorPoint().SumOfProducts(proofG, a)
hb := prover.curve.NewGeneratorPoint().SumOfProducts(proofH, b)
uadotb := u.Mul(c)
capP := ga.Add(hb).Add(uadotb)
return capP, nil
}
// Prove executes the prover protocol on pg 16 of https://eprint.iacr.org/2017/1066.pdf
// It generates an inner product proof for vectors a and b, using u to blind the inner product in P
// A transcript is used for the Fiat Shamir heuristic.
func (prover *InnerProductProver) Prove(a, b []curves.Scalar, u curves.Point, transcript *merlin.Transcript) (*InnerProductProof, error) {
// Vectors must have length power of two
if !isPowerOfTwo(len(a)) {
return nil, errors.New("ipp vector length must be power of two")
}
// Generator vectors must be same length
if len(prover.generators.G) != len(prover.generators.H) {
return nil, errors.New("ipp generator lengths of g and h must be equal")
}
// Inner product requires len(a) == len(b) else error is returned
c, err := innerProduct(a, b)
if err != nil {
return nil, errors.Wrap(err, "ipp getInnerProduct")
}
// Length of vectors must be less than the number of generators generated
if len(a) > len(prover.generators.G) {
return nil, errors.New("ipp vector length must be less than maxVectorLength")
}
// In case where len(a) is less than number of generators precomputed by prover, trim to length
proofG := prover.generators.G[0:len(a)]
proofH := prover.generators.H[0:len(b)]
// initial P = g^a * h^b * u^(a dot b) (See (3) on page 13 of https://eprint.iacr.org/2017/1066.pdf)
ga := prover.curve.NewGeneratorPoint().SumOfProducts(proofG, a)
hb := prover.curve.NewGeneratorPoint().SumOfProducts(proofH, b)
uadotb := u.Mul(c)
capP := ga.Add(hb).Add(uadotb)
recursionParams := &ippRecursion{
a: a,
b: b,
capLs: []curves.Point{},
capRs: []curves.Point{},
c: c,
g: proofG,
h: proofH,
capP: capP,
u: u,
transcript: transcript,
}
return prover.proveRecursive(recursionParams)
}
// proveRecursive executes the recursion on pg 16 of https://eprint.iacr.org/2017/1066.pdf
func (prover *InnerProductProver) proveRecursive(recursionParams *ippRecursion) (*InnerProductProof, error) {
// length checks
if len(recursionParams.a) != len(recursionParams.b) {
return nil, errors.New("ipp proveRecursive a and b different lengths")
}
if len(recursionParams.g) != len(recursionParams.h) {
return nil, errors.New("ipp proveRecursive g and h different lengths")
}
if len(recursionParams.a) != len(recursionParams.g) {
return nil, errors.New("ipp proveRecursive scalar and point vectors different lengths")
}
// Base case (L14, pg16 of https://eprint.iacr.org/2017/1066.pdf)
if len(recursionParams.a) == 1 {
proof := &InnerProductProof{
a: recursionParams.a[0],
b: recursionParams.b[0],
capLs: recursionParams.capLs,
capRs: recursionParams.capRs,
curve: &prover.curve,
}
return proof, nil
}
// Split current state into low (first half) vs high (second half) vectors
aLo, aHi, err := splitScalarVector(recursionParams.a)
if err != nil {
return nil, errors.Wrap(err, "recursionParams splitScalarVector")
}
bLo, bHi, err := splitScalarVector(recursionParams.b)
if err != nil {
return nil, errors.Wrap(err, "recursionParams splitScalarVector")
}
gLo, gHi, err := splitPointVector(recursionParams.g)
if err != nil {
return nil, errors.Wrap(err, "recursionParams splitPointVector")
}
hLo, hHi, err := splitPointVector(recursionParams.h)
if err != nil {
return nil, errors.Wrap(err, "recursionParams splitPointVector")
}
// c_l, c_r (L21,22, pg16 of https://eprint.iacr.org/2017/1066.pdf)
cL, err := innerProduct(aLo, bHi)
if err != nil {
return nil, errors.Wrap(err, "recursionParams innerProduct")
}
cR, err := innerProduct(aHi, bLo)
if err != nil {
return nil, errors.Wrap(err, "recursionParams innerProduct")
}
// L, R (L23,24, pg16 of https://eprint.iacr.org/2017/1066.pdf)
lga := prover.curve.Point.SumOfProducts(gHi, aLo)
lhb := prover.curve.Point.SumOfProducts(hLo, bHi)
ucL := recursionParams.u.Mul(cL)
capL := lga.Add(lhb).Add(ucL)
rga := prover.curve.Point.SumOfProducts(gLo, aHi)
rhb := prover.curve.Point.SumOfProducts(hHi, bLo)
ucR := recursionParams.u.Mul(cR)
capR := rga.Add(rhb).Add(ucR)
// Add L,R for verifier to use to calculate final g, h
newL := recursionParams.capLs
newL = append(newL, capL)
newR := recursionParams.capRs
newR = append(newR, capR)
// Get x from L, R for non-interactive (See section 4.4 pg22 of https://eprint.iacr.org/2017/1066.pdf)
// Note this replaces the interactive model, i.e. L36-28 of pg16 of https://eprint.iacr.org/2017/1066.pdf
x, err := prover.calcx(capL, capR, recursionParams.transcript)
if err != nil {
return nil, errors.Wrap(err, "recursionParams calcx")
}
// Calculate recursive inputs
xInv, err := x.Invert()
if err != nil {
return nil, errors.Wrap(err, "recursionParams x.Invert")
}
// g', h' (L29,30, pg16 of https://eprint.iacr.org/2017/1066.pdf)
gLoxInverse := multiplyScalarToPointVector(xInv, gLo)
gHix := multiplyScalarToPointVector(x, gHi)
gPrime, err := multiplyPairwisePointVectors(gLoxInverse, gHix)
if err != nil {
return nil, errors.Wrap(err, "recursionParams multiplyPairwisePointVectors")
}
hLox := multiplyScalarToPointVector(x, hLo)
hHixInv := multiplyScalarToPointVector(xInv, hHi)
hPrime, err := multiplyPairwisePointVectors(hLox, hHixInv)
if err != nil {
return nil, errors.Wrap(err, "recursionParams multiplyPairwisePointVectors")
}
// P' (L31, pg16 of https://eprint.iacr.org/2017/1066.pdf)
xSquare := x.Square()
xInvSquare := xInv.Square()
LxSquare := capL.Mul(xSquare)
RxInvSquare := capR.Mul(xInvSquare)
PPrime := LxSquare.Add(recursionParams.capP).Add(RxInvSquare)
// a', b' (L33, 34, pg16 of https://eprint.iacr.org/2017/1066.pdf)
aLox := multiplyScalarToScalarVector(x, aLo)
aHixIn := multiplyScalarToScalarVector(xInv, aHi)
aPrime, err := addPairwiseScalarVectors(aLox, aHixIn)
if err != nil {
return nil, errors.Wrap(err, "recursionParams addPairwiseScalarVectors")
}
bLoxInv := multiplyScalarToScalarVector(xInv, bLo)
bHix := multiplyScalarToScalarVector(x, bHi)
bPrime, err := addPairwiseScalarVectors(bLoxInv, bHix)
if err != nil {
return nil, errors.Wrap(err, "recursionParams addPairwiseScalarVectors")
}
// c'
cPrime, err := innerProduct(aPrime, bPrime)
if err != nil {
return nil, errors.Wrap(err, "recursionParams innerProduct")
}
// Make recursive call (L35, pg16 of https://eprint.iacr.org/2017/1066.pdf)
recursiveIPP := &ippRecursion{
a: aPrime,
b: bPrime,
capLs: newL,
capRs: newR,
c: cPrime,
g: gPrime,
h: hPrime,
capP: PPrime,
u: recursionParams.u,
transcript: recursionParams.transcript,
}
out, err := prover.proveRecursive(recursiveIPP)
if err != nil {
return nil, errors.Wrap(err, "recursionParams proveRecursive")
}
return out, nil
}
// calcx uses a merlin transcript for Fiat Shamir
// For each recursion, it takes the current state of the transcript and appends the newly calculated L and R values
// A new scalar is then read from the transcript
// See section 4.4 pg22 of https://eprint.iacr.org/2017/1066.pdf
func (prover *InnerProductProver) calcx(capL, capR curves.Point, transcript *merlin.Transcript) (curves.Scalar, error) {
// Add the newest capL and capR values to transcript
transcript.AppendMessage([]byte("addRecursiveL"), capL.ToAffineUncompressed())
transcript.AppendMessage([]byte("addRecursiveR"), capR.ToAffineUncompressed())
// Read 64 bytes from, set to scalar
outBytes := transcript.ExtractBytes([]byte("getx"), 64)
x, err := prover.curve.NewScalar().SetBytesWide(outBytes)
if err != nil {
return nil, errors.Wrap(err, "calcx NewScalar SetBytesWide")
}
return x, nil
}
// MarshalBinary takes an inner product proof and marshals into bytes.
func (proof *InnerProductProof) MarshalBinary() []byte {
var out []byte
out = append(out, proof.a.Bytes()...)
out = append(out, proof.b.Bytes()...)
for i, capLElem := range proof.capLs {
capRElem := proof.capRs[i]
out = append(out, capLElem.ToAffineCompressed()...)
out = append(out, capRElem.ToAffineCompressed()...)
}
return out
}
// UnmarshalBinary takes bytes of a marshaled proof and writes them into an inner product proof
// The inner product proof used should be from the output of NewInnerProductProof().
func (proof *InnerProductProof) UnmarshalBinary(data []byte) error {
scalarLen := len(proof.curve.NewScalar().Bytes())
pointLen := len(proof.curve.NewGeneratorPoint().ToAffineCompressed())
ptr := 0
// Get scalars
a, err := proof.curve.NewScalar().SetBytes(data[ptr : ptr+scalarLen])
if err != nil {
return errors.New("innerProductProof UnmarshalBinary SetBytes")
}
proof.a = a
ptr += scalarLen
b, err := proof.curve.NewScalar().SetBytes(data[ptr : ptr+scalarLen])
if err != nil {
return errors.New("innerProductProof UnmarshalBinary SetBytes")
}
proof.b = b
ptr += scalarLen
// Get points
var capLs, capRs []curves.Point //nolint:prealloc // pointer arithmetic makes it too unreadable.
for ptr < len(data) {
capLElem, err := proof.curve.Point.FromAffineCompressed(data[ptr : ptr+pointLen])
if err != nil {
return errors.New("innerProductProof UnmarshalBinary FromAffineCompressed")
}
capLs = append(capLs, capLElem)
ptr += pointLen
capRElem, err := proof.curve.Point.FromAffineCompressed(data[ptr : ptr+pointLen])
if err != nil {
return errors.New("innerProductProof UnmarshalBinary FromAffineCompressed")
}
capRs = append(capRs, capRElem)
ptr += pointLen
}
proof.capLs = capLs
proof.capRs = capRs
return nil
}
+99
View File
@@ -0,0 +1,99 @@
package bulletproof
import (
crand "crypto/rand"
"testing"
"github.com/gtank/merlin"
"github.com/stretchr/testify/require"
"github.com/onsonr/sonr/pkg/crypto/core/curves"
)
func TestIPPHappyPath(t *testing.T) {
curve := curves.ED25519()
prover, err := NewInnerProductProver(8, []byte("test"), *curve)
require.NoError(t, err)
a := randScalarVec(8, *curve)
b := randScalarVec(8, *curve)
u := curve.Point.Random(crand.Reader)
transcript := merlin.NewTranscript("test")
proof, err := prover.Prove(a, b, u, transcript)
require.NoError(t, err)
require.Equal(t, 3, len(proof.capLs))
require.Equal(t, 3, len(proof.capRs))
}
func TestIPPMismatchedVectors(t *testing.T) {
curve := curves.ED25519()
prover, err := NewInnerProductProver(8, []byte("test"), *curve)
require.NoError(t, err)
a := randScalarVec(4, *curve)
b := randScalarVec(8, *curve)
u := curve.Point.Random(crand.Reader)
transcript := merlin.NewTranscript("test")
_, err = prover.Prove(a, b, u, transcript)
require.Error(t, err)
}
func TestIPPNonPowerOfTwoLengthVectors(t *testing.T) {
curve := curves.ED25519()
prover, err := NewInnerProductProver(8, []byte("test"), *curve)
require.NoError(t, err)
a := randScalarVec(3, *curve)
b := randScalarVec(3, *curve)
u := curve.Point.Random(crand.Reader)
transcript := merlin.NewTranscript("test")
_, err = prover.Prove(a, b, u, transcript)
require.Error(t, err)
}
func TestIPPZeroLengthVectors(t *testing.T) {
curve := curves.ED25519()
prover, err := NewInnerProductProver(8, []byte("test"), *curve)
require.NoError(t, err)
a := randScalarVec(0, *curve)
b := randScalarVec(0, *curve)
u := curve.Point.Random(crand.Reader)
transcript := merlin.NewTranscript("test")
_, err = prover.Prove(a, b, u, transcript)
require.Error(t, err)
}
func TestIPPGreaterThanMaxLengthVectors(t *testing.T) {
curve := curves.ED25519()
prover, err := NewInnerProductProver(8, []byte("test"), *curve)
require.NoError(t, err)
a := randScalarVec(16, *curve)
b := randScalarVec(16, *curve)
u := curve.Point.Random(crand.Reader)
transcript := merlin.NewTranscript("test")
_, err = prover.Prove(a, b, u, transcript)
require.Error(t, err)
}
func TestIPPMarshal(t *testing.T) {
curve := curves.ED25519()
prover, err := NewInnerProductProver(8, []byte("test"), *curve)
require.NoError(t, err)
a := randScalarVec(8, *curve)
b := randScalarVec(8, *curve)
u := curve.Point.Random(crand.Reader)
transcript := merlin.NewTranscript("test")
proof, err := prover.Prove(a, b, u, transcript)
require.NoError(t, err)
proofMarshaled := proof.MarshalBinary()
proofPrime := NewInnerProductProof(curve)
err = proofPrime.UnmarshalBinary(proofMarshaled)
require.NoError(t, err)
require.Zero(t, proof.a.Cmp(proofPrime.a))
require.Zero(t, proof.b.Cmp(proofPrime.b))
for i, proofCapLElem := range proof.capLs {
proofPrimeCapLElem := proofPrime.capLs[i]
require.True(t, proofCapLElem.Equal(proofPrimeCapLElem))
proofCapRElem := proof.capRs[i]
proofPrimeCapRElem := proofPrime.capRs[i]
require.True(t, proofCapRElem.Equal(proofPrimeCapRElem))
}
}
+209
View File
@@ -0,0 +1,209 @@
package bulletproof
import (
"github.com/gtank/merlin"
"github.com/pkg/errors"
"github.com/onsonr/sonr/pkg/crypto/core/curves"
)
// InnerProductVerifier is the struct used to verify inner product proofs
// It specifies which curve to use and holds precomputed generators
// See NewInnerProductProver() for prover initialization.
type InnerProductVerifier struct {
curve curves.Curve
generators ippGenerators
}
// NewInnerProductVerifier initializes a new verifier
// It uses the specified domain to generate generators for vectors of at most maxVectorLength
// A verifier can be used to verify inner product proofs for vectors of length less than or equal to maxVectorLength
// A verifier is defined by an explicit curve.
func NewInnerProductVerifier(maxVectorLength int, domain []byte, curve curves.Curve) (*InnerProductVerifier, error) {
generators, err := getGeneratorPoints(maxVectorLength, domain, curve)
if err != nil {
return nil, errors.Wrap(err, "ipp getGenerators")
}
return &InnerProductVerifier{curve: curve, generators: *generators}, nil
}
// Verify verifies the given proof inputs
// It implements the final comparison of section 3.1 on pg17 of https://eprint.iacr.org/2017/1066.pdf
func (verifier *InnerProductVerifier) Verify(capP, u curves.Point, proof *InnerProductProof, transcript *merlin.Transcript) (bool, error) {
if len(proof.capLs) != len(proof.capRs) {
return false, errors.New("ipp capLs and capRs must be same length")
}
// Generator vectors must be same length
if len(verifier.generators.G) != len(verifier.generators.H) {
return false, errors.New("ipp generator lengths of g and h must be equal")
}
// Get generators for each elem in a, b and one more for u
// len(Ls) = log n, therefore can just exponentiate
n := 1 << len(proof.capLs)
// Length of vectors must be less than the number of generators generated
if n > len(verifier.generators.G) {
return false, errors.New("ipp vector length must be less than maxVectorLength")
}
// In case where len(a) is less than number of generators precomputed by prover, trim to length
proofG := verifier.generators.G[0:n]
proofH := verifier.generators.H[0:n]
xs, err := getxs(transcript, proof.capLs, proof.capRs, verifier.curve)
if err != nil {
return false, errors.Wrap(err, "verifier getxs")
}
s, err := verifier.getsNew(xs, n)
if err != nil {
return false, errors.Wrap(err, "verifier getss")
}
lhs, err := verifier.getLHS(u, proof, proofG, proofH, s)
if err != nil {
return false, errors.Wrap(err, "verify getLHS")
}
rhs, err := verifier.getRHS(capP, proof, xs)
if err != nil {
return false, errors.Wrap(err, "verify getRHS")
}
return lhs.Equal(rhs), nil
}
// Verify verifies the given proof inputs
// It implements the final comparison of section 3.1 on pg17 of https://eprint.iacr.org/2017/1066.pdf
func (verifier *InnerProductVerifier) VerifyFromRangeProof(proofG, proofH []curves.Point, capPhmuinv, u curves.Point, tHat curves.Scalar, proof *InnerProductProof, transcript *merlin.Transcript) (bool, error) {
// Get generators for each elem in a, b and one more for u
// len(Ls) = log n, therefore can just exponentiate
n := 1 << len(proof.capLs)
xs, err := getxs(transcript, proof.capLs, proof.capRs, verifier.curve)
if err != nil {
return false, errors.Wrap(err, "verifier getxs")
}
s, err := verifier.gets(xs, n)
if err != nil {
return false, errors.Wrap(err, "verifier getss")
}
lhs, err := verifier.getLHS(u, proof, proofG, proofH, s)
if err != nil {
return false, errors.Wrap(err, "verify getLHS")
}
utHat := u.Mul(tHat)
capP := capPhmuinv.Add(utHat)
rhs, err := verifier.getRHS(capP, proof, xs)
if err != nil {
return false, errors.Wrap(err, "verify getRHS")
}
return lhs.Equal(rhs), nil
}
// getRHS gets the right hand side of the final comparison of section 3.1 on pg17.
func (*InnerProductVerifier) getRHS(capP curves.Point, proof *InnerProductProof, xs []curves.Scalar) (curves.Point, error) {
product := capP
for j, Lj := range proof.capLs {
Rj := proof.capRs[j]
xj := xs[j]
xjSquare := xj.Square()
xjSquareInv, err := xjSquare.Invert()
if err != nil {
return nil, errors.Wrap(err, "verify invert")
}
LjxjSquare := Lj.Mul(xjSquare)
RjxjSquareInv := Rj.Mul(xjSquareInv)
product = product.Add(LjxjSquare).Add(RjxjSquareInv)
}
return product, nil
}
// getLHS gets the left hand side of the final comparison of section 3.1 on pg17.
func (verifier *InnerProductVerifier) getLHS(u curves.Point, proof *InnerProductProof, g, h []curves.Point, s []curves.Scalar) (curves.Point, error) {
sInv, err := invertScalars(s)
if err != nil {
return nil, errors.Wrap(err, "verify invertScalars")
}
// g^(a*s)
as := multiplyScalarToScalarVector(proof.a, s)
gas := verifier.curve.Point.SumOfProducts(g, as)
// h^(b*s^-1)
bsInv := multiplyScalarToScalarVector(proof.b, sInv)
hbsInv := verifier.curve.Point.SumOfProducts(h, bsInv)
// u^a*b
ab := proof.a.Mul(proof.b)
uab := u.Mul(ab)
// g^(a*s) * h^(b*s^-1) * u^a*b
out := gas.Add(hbsInv).Add(uab)
return out, nil
}
// getxs calculates the x values from Ls and Rs
// Note that each x is read from the transcript, then the L and R at a certain index are written to the transcript
// This mirrors the reading of xs and writing of Ls and Rs in the prover.
func getxs(transcript *merlin.Transcript, capLs, capRs []curves.Point, curve curves.Curve) ([]curves.Scalar, error) {
xs := make([]curves.Scalar, len(capLs))
for i, capLi := range capLs {
capRi := capRs[i]
// Add the newest L and R values to transcript
transcript.AppendMessage([]byte("addRecursiveL"), capLi.ToAffineUncompressed())
transcript.AppendMessage([]byte("addRecursiveR"), capRi.ToAffineUncompressed())
// Read 64 bytes from, set to scalar
outBytes := transcript.ExtractBytes([]byte("getx"), 64)
x, err := curve.NewScalar().SetBytesWide(outBytes)
if err != nil {
return nil, errors.Wrap(err, "calcx NewScalar SetBytesWide")
}
xs[i] = x
}
return xs, nil
}
// gets calculates the vector s of values used for verification
// See the second expression of section 3.1 on pg15
// nolint
func (verifier *InnerProductVerifier) gets(xs []curves.Scalar, n int) ([]curves.Scalar, error) {
ss := make([]curves.Scalar, n)
for i := 0; i < n; i++ {
si := verifier.curve.Scalar.One()
for j, xj := range xs {
if i>>(len(xs)-j-1)&0x01 == 1 {
si = si.Mul(xj)
} else {
xjInverse, err := xj.Invert()
if err != nil {
return nil, errors.Wrap(err, "getss invert")
}
si = si.Mul(xjInverse)
}
}
ss[i] = si
}
return ss, nil
}
// getsNew calculates the vector s of values used for verification
// It provides analogous functionality as gets(), but uses a O(n) algorithm vs O(nlogn)
// The algorithm inverts all xs, then begins multiplying the inversion by the square of x elements to
// calculate all s values thus minimizing necessary inversions/ computation.
func (verifier *InnerProductVerifier) getsNew(xs []curves.Scalar, n int) ([]curves.Scalar, error) {
var err error
ss := make([]curves.Scalar, n)
// First element is all xs inverted mul'd
ss[0] = verifier.curve.Scalar.One()
for _, xj := range xs {
ss[0] = ss[0].Mul(xj)
}
ss[0], err = ss[0].Invert()
if err != nil {
return nil, errors.Wrap(err, "ipp gets inv ss0")
}
for j, xj := range xs {
xjSquared := xj.Square()
for i := 0; i < n; i += 1 << (len(xs) - j) {
ss[i+1<<(len(xs)-j-1)] = ss[i].Mul(xjSquared)
}
}
return ss, nil
}
+79
View File
@@ -0,0 +1,79 @@
package bulletproof
import (
crand "crypto/rand"
"testing"
"github.com/gtank/merlin"
"github.com/stretchr/testify/require"
"github.com/onsonr/sonr/pkg/crypto/core/curves"
)
func TestIPPVerifyHappyPath(t *testing.T) {
curve := curves.ED25519()
vecLength := 256
prover, err := NewInnerProductProver(vecLength, []byte("test"), *curve)
require.NoError(t, err)
a := randScalarVec(vecLength, *curve)
b := randScalarVec(vecLength, *curve)
u := curve.Point.Random(crand.Reader)
transcriptProver := merlin.NewTranscript("test")
proof, err := prover.Prove(a, b, u, transcriptProver)
require.NoError(t, err)
verifier, err := NewInnerProductVerifier(vecLength, []byte("test"), *curve)
require.NoError(t, err)
capP, err := prover.getP(a, b, u)
require.NoError(t, err)
transcriptVerifier := merlin.NewTranscript("test")
verified, err := verifier.Verify(capP, u, proof, transcriptVerifier)
require.NoError(t, err)
require.True(t, verified)
}
func BenchmarkIPPVerification(bench *testing.B) {
curve := curves.ED25519()
vecLength := 1024
prover, _ := NewInnerProductProver(vecLength, []byte("test"), *curve)
a := randScalarVec(vecLength, *curve)
b := randScalarVec(vecLength, *curve)
u := curve.Point.Random(crand.Reader)
transcriptProver := merlin.NewTranscript("test")
proof, _ := prover.Prove(a, b, u, transcriptProver)
verifier, _ := NewInnerProductVerifier(vecLength, []byte("test"), *curve)
capP, _ := prover.getP(a, b, u)
transcriptVerifier := merlin.NewTranscript("test")
verified, _ := verifier.Verify(capP, u, proof, transcriptVerifier)
require.True(bench, verified)
}
func TestIPPVerifyInvalidProof(t *testing.T) {
curve := curves.ED25519()
vecLength := 64
prover, err := NewInnerProductProver(vecLength, []byte("test"), *curve)
require.NoError(t, err)
a := randScalarVec(vecLength, *curve)
b := randScalarVec(vecLength, *curve)
u := curve.Point.Random(crand.Reader)
aPrime := randScalarVec(64, *curve)
bPrime := randScalarVec(64, *curve)
uPrime := curve.Point.Random(crand.Reader)
transcriptProver := merlin.NewTranscript("test")
proofPrime, err := prover.Prove(aPrime, bPrime, uPrime, transcriptProver)
require.NoError(t, err)
verifier, err := NewInnerProductVerifier(vecLength, []byte("test"), *curve)
require.NoError(t, err)
capP, err := prover.getP(a, b, u)
require.NoError(t, err)
transcriptVerifier := merlin.NewTranscript("test")
// Check for different capP, u from proof
verified, err := verifier.Verify(capP, u, proofPrime, transcriptVerifier)
require.NoError(t, err)
require.False(t, verified)
}
+348
View File
@@ -0,0 +1,348 @@
package bulletproof
import (
crand "crypto/rand"
"github.com/gtank/merlin"
"github.com/pkg/errors"
"github.com/onsonr/sonr/pkg/crypto/core/curves"
)
// BatchProve proves that a list of scalars v are in the range n.
// It implements the aggregating logarithmic proofs defined on pg21.
// Instead of taking a single value and a single blinding factor, BatchProve takes in a list of values and list of
// blinding factors.
func (prover *RangeProver) BatchProve(v, gamma []curves.Scalar, n int, proofGenerators RangeProofGenerators, transcript *merlin.Transcript) (*RangeProof, error) {
// Define nm as the total bits required for secrets, calculated as number of secrets * n
m := len(v)
nm := n * m
// nm must be less than or equal to the number of generators generated
if nm > len(prover.generators.G) {
return nil, errors.New("ipp vector length must be less than or equal to maxVectorLength")
}
// In case where nm is less than number of generators precomputed by prover, trim to length
proofG := prover.generators.G[0:nm]
proofH := prover.generators.H[0:nm]
// Check that each elem in v is in range [0, 2^n]
for _, vi := range v {
checkedRange := checkRange(vi, n)
if checkedRange != nil {
return nil, checkedRange
}
}
// L40 on pg19
aL, err := getaLBatched(v, n, prover.curve)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
onenm := get1nVector(nm, prover.curve)
// L41 on pg19
aR, err := subtractPairwiseScalarVectors(aL, onenm)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
alpha := prover.curve.Scalar.Random(crand.Reader)
// Calc A (L44, pg19)
halpha := proofGenerators.h.Mul(alpha)
gaL := prover.curve.Point.SumOfProducts(proofG, aL)
haR := prover.curve.Point.SumOfProducts(proofH, aR)
capA := halpha.Add(gaL).Add(haR)
// L45, 46, pg19
sL := getBlindingVector(nm, prover.curve)
sR := getBlindingVector(nm, prover.curve)
rho := prover.curve.Scalar.Random(crand.Reader)
// Calc S (L47, pg19)
hrho := proofGenerators.h.Mul(rho)
gsL := prover.curve.Point.SumOfProducts(proofG, sL)
hsR := prover.curve.Point.SumOfProducts(proofH, sR)
capS := hrho.Add(gsL).Add(hsR)
// Fiat Shamir for y,z (L49, pg19)
capV := getcapVBatched(v, gamma, proofGenerators.g, proofGenerators.h)
y, z, err := calcyzBatched(capV, capA, capS, transcript, prover.curve)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
// Calc t_1, t_2
// See the l(X), r(X), equations on pg 21
// Use l(X)'s and r(X)'s constant and linear terms to derive t_1 and t_2
// (a_l - z*1^n)
zonenm := multiplyScalarToScalarVector(z, onenm)
constantTerml, err := subtractPairwiseScalarVectors(aL, zonenm)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
linearTerml := sL
// zSum term, see equation 71 on pg21
zSum := getSumTermrXBatched(z, n, len(v), prover.curve)
// a_r + z*1^nm
aRPluszonenm, err := addPairwiseScalarVectors(aR, zonenm)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
ynm := getknVector(y, nm, prover.curve)
hadamard, err := multiplyPairwiseScalarVectors(ynm, aRPluszonenm)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
constantTermr, err := addPairwiseScalarVectors(hadamard, zSum)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
linearTermr, err := multiplyPairwiseScalarVectors(ynm, sR)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
// t_1 (as the linear coefficient) is the sum of the dot products of l(X)'s linear term dot r(X)'s constant term
// and r(X)'s linear term dot l(X)'s constant term
t1FirstTerm, err := innerProduct(linearTerml, constantTermr)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
t1SecondTerm, err := innerProduct(linearTermr, constantTerml)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
t1 := t1FirstTerm.Add(t1SecondTerm)
// t_2 (as the quadratic coefficient) is the dot product of l(X)'s and r(X)'s linear terms
t2, err := innerProduct(linearTerml, linearTermr)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
// L52, pg20
tau1 := prover.curve.Scalar.Random(crand.Reader)
tau2 := prover.curve.Scalar.Random(crand.Reader)
// T_1, T_2 (L53, pg20)
capT1 := proofGenerators.g.Mul(t1).Add(proofGenerators.h.Mul(tau1))
capT2 := proofGenerators.g.Mul(t2).Add(proofGenerators.h.Mul(tau2))
// Fiat shamir for x (L55, pg20)
x, err := calcx(capT1, capT2, transcript, prover.curve)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
// Calc l
// Instead of using the expression in the line, evaluate l() at x
sLx := multiplyScalarToScalarVector(x, linearTerml)
l, err := addPairwiseScalarVectors(constantTerml, sLx)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
// Calc r
// Instead of using the expression in the line, evaluate r() at x
ynsRx := multiplyScalarToScalarVector(x, linearTermr)
r, err := addPairwiseScalarVectors(constantTermr, ynsRx)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
// Calc t hat
// For efficiency, instead of calculating the dot product, evaluate t() at x
zm := getknVector(z, m, prover.curve)
zsquarezm := multiplyScalarToScalarVector(z.Square(), zm)
sumv := prover.curve.Scalar.Zero()
for i := 0; i < m; i++ {
elem := zsquarezm[i].Mul(v[i])
sumv = sumv.Add(elem)
}
deltayzBatched, err := deltayzBatched(y, z, n, m, prover.curve)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
t0 := sumv.Add(deltayzBatched)
tLinear := t1.Mul(x)
tQuadratic := t2.Mul(x.Square())
tHat := t0.Add(tLinear).Add(tQuadratic)
// Calc tau_x (L61, pg20)
tau2xsquare := tau2.Mul(x.Square())
tau1x := tau1.Mul(x)
zsum := prover.curve.Scalar.Zero()
zExp := z.Clone()
for j := 1; j < m+1; j++ {
zExp = zExp.Mul(z)
zsum = zsum.Add(zExp.Mul(gamma[j-1]))
}
taux := tau2xsquare.Add(tau1x).Add(zsum)
// Calc mu (L62, pg20)
mu := alpha.Add(rho.Mul(x))
// Calc IPP (See section 4.2)
hPrime, err := gethPrime(proofH, y, prover.curve)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
// P is redefined in batched case, see bottom equation on pg21.
capPhmu := getPhmuBatched(proofG, hPrime, proofGenerators.h, capA, capS, x, y, z, mu, n, m, prover.curve)
wBytes := transcript.ExtractBytes([]byte("getw"), 64)
w, err := prover.curve.NewScalar().SetBytesWide(wBytes)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
ipp, err := prover.ippProver.rangeToIPP(proofG, hPrime, l, r, tHat, capPhmu, proofGenerators.u.Mul(w), transcript)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
out := &RangeProof{
capA: capA,
capS: capS,
capT1: capT1,
capT2: capT2,
taux: taux,
mu: mu,
tHat: tHat,
ipp: ipp,
curve: &prover.curve,
}
return out, nil
}
// See final term of L71 on pg 21
// Sigma_{j=1}^{m} z^{1+j} * (0^{(j-1)*n} || 2^{n} || 0^{(m-j)*n}).
func getSumTermrXBatched(z curves.Scalar, n, m int, curve curves.Curve) []curves.Scalar {
twoN := get2nVector(n, curve)
var out []curves.Scalar
// The final power should be one more than m
zExp := z.Clone()
for j := 0; j < m; j++ {
zExp = zExp.Mul(z)
elem := multiplyScalarToScalarVector(zExp, twoN)
out = append(out, elem...)
}
return out
}
func getcapVBatched(v, gamma []curves.Scalar, g, h curves.Point) []curves.Point {
out := make([]curves.Point, len(v))
for i, vi := range v {
out[i] = getcapV(vi, gamma[i], g, h)
}
return out
}
func getaLBatched(v []curves.Scalar, n int, curve curves.Curve) ([]curves.Scalar, error) {
var aL []curves.Scalar
for _, vi := range v {
aLi, err := getaL(vi, n, curve)
if err != nil {
return nil, err
}
aL = append(aL, aLi...)
}
return aL, nil
}
func calcyzBatched(capV []curves.Point, capA, capS curves.Point, transcript *merlin.Transcript, curve curves.Curve) (curves.Scalar, curves.Scalar, error) {
// Add the A,S values to transcript
for _, capVi := range capV {
transcript.AppendMessage([]byte("addV"), capVi.ToAffineUncompressed())
}
transcript.AppendMessage([]byte("addcapA"), capA.ToAffineUncompressed())
transcript.AppendMessage([]byte("addcapS"), capS.ToAffineUncompressed())
// Read 64 bytes twice from, set to scalar for y and z
yBytes := transcript.ExtractBytes([]byte("gety"), 64)
y, err := curve.NewScalar().SetBytesWide(yBytes)
if err != nil {
return nil, nil, errors.Wrap(err, "calcyz NewScalar SetBytesWide")
}
zBytes := transcript.ExtractBytes([]byte("getz"), 64)
z, err := curve.NewScalar().SetBytesWide(zBytes)
if err != nil {
return nil, nil, errors.Wrap(err, "calcyz NewScalar SetBytesWide")
}
return y, z, nil
}
func deltayzBatched(y, z curves.Scalar, n, m int, curve curves.Curve) (curves.Scalar, error) {
// z - z^2
zMinuszsquare := z.Sub(z.Square())
// 1^(n*m)
onenm := get1nVector(n*m, curve)
// <1^nm, y^nm>
onenmdotynm, err := innerProduct(onenm, getknVector(y, n*m, curve))
if err != nil {
return nil, errors.Wrap(err, "deltayz")
}
// (z - z^2)*<1^n, y^n>
termFirst := zMinuszsquare.Mul(onenmdotynm)
// <1^n, 2^n>
onendottwon, err := innerProduct(get1nVector(n, curve), get2nVector(n, curve))
if err != nil {
return nil, errors.Wrap(err, "deltayz")
}
termSecond := curve.Scalar.Zero()
zExp := z.Square()
for j := 1; j < m+1; j++ {
zExp = zExp.Mul(z)
elem := zExp.Mul(onendottwon)
termSecond = termSecond.Add(elem)
}
// (z - z^2)*<1^n, y^n> - z^3*<1^n, 2^n>
out := termFirst.Sub(termSecond)
return out, nil
}
// Bottom equation on pg21.
func getPhmuBatched(proofG, proofHPrime []curves.Point, h, capA, capS curves.Point, x, y, z, mu curves.Scalar, n, m int, curve curves.Curve) curves.Point {
twoN := get2nVector(n, curve)
// h'^(z*y^n + z^2*2^n)
lastElem := curve.NewIdentityPoint()
zExp := z.Clone()
for j := 1; j < m+1; j++ {
// Get subvector of h
hSubvector := proofHPrime[(j-1)*n : j*n]
// z^(j+1)
zExp = zExp.Mul(z)
exp := multiplyScalarToScalarVector(zExp, twoN)
// Final elem
elem := curve.Point.SumOfProducts(hSubvector, exp)
lastElem = lastElem.Add(elem)
}
zynm := multiplyScalarToScalarVector(z, getknVector(y, n*m, curve))
hPrimezynm := curve.Point.SumOfProducts(proofHPrime, zynm)
lastElem = lastElem.Add(hPrimezynm)
// S^x
capSx := capS.Mul(x)
// g^-z --> -z*<1,g>
onenm := get1nVector(n*m, curve)
zNeg := z.Neg()
zinvonen := multiplyScalarToScalarVector(zNeg, onenm)
zgdotonen := curve.Point.SumOfProducts(proofG, zinvonen)
// L66 on pg20
P := capA.Add(capSx).Add(zgdotonen).Add(lastElem)
hmu := h.Mul(mu)
Phmu := P.Sub(hmu)
return Phmu
}
+102
View File
@@ -0,0 +1,102 @@
package bulletproof
import (
crand "crypto/rand"
"testing"
"github.com/gtank/merlin"
"github.com/stretchr/testify/require"
"github.com/onsonr/sonr/pkg/crypto/core/curves"
)
func TestRangeBatchProverHappyPath(t *testing.T) {
curve := curves.ED25519()
n := 256
prover, err := NewRangeProver(n*4, []byte("rangeDomain"), []byte("ippDomain"), *curve)
require.NoError(t, err)
v1 := curve.Scalar.Random(crand.Reader)
v2 := curve.Scalar.Random(crand.Reader)
v3 := curve.Scalar.Random(crand.Reader)
v4 := curve.Scalar.Random(crand.Reader)
v := []curves.Scalar{v1, v2, v3, v4}
gamma1 := curve.Scalar.Random(crand.Reader)
gamma2 := curve.Scalar.Random(crand.Reader)
gamma3 := curve.Scalar.Random(crand.Reader)
gamma4 := curve.Scalar.Random(crand.Reader)
gamma := []curves.Scalar{gamma1, gamma2, gamma3, gamma4}
g := curve.Point.Random(crand.Reader)
h := curve.Point.Random(crand.Reader)
u := curve.Point.Random(crand.Reader)
proofGenerators := RangeProofGenerators{
g: g,
h: h,
u: u,
}
transcript := merlin.NewTranscript("test")
proof, err := prover.BatchProve(v, gamma, n, proofGenerators, transcript)
require.NoError(t, err)
require.NotNil(t, proof)
require.Equal(t, 10, len(proof.ipp.capLs))
require.Equal(t, 10, len(proof.ipp.capRs))
}
func TestGetaLBatched(t *testing.T) {
curve := curves.ED25519()
v1 := curve.Scalar.Random(crand.Reader)
v2 := curve.Scalar.Random(crand.Reader)
v3 := curve.Scalar.Random(crand.Reader)
v4 := curve.Scalar.Random(crand.Reader)
v := []curves.Scalar{v1, v2, v3, v4}
aL, err := getaLBatched(v, 256, *curve)
require.NoError(t, err)
twoN := get2nVector(256, *curve)
for i := 1; i < len(v)+1; i++ {
vec := aL[(i-1)*256 : i*256]
product, err := innerProduct(vec, twoN)
require.NoError(t, err)
require.Zero(t, product.Cmp(v[i-1]))
}
}
func TestRangeBatchProverMarshal(t *testing.T) {
curve := curves.ED25519()
n := 256
prover, err := NewRangeProver(n*4, []byte("rangeDomain"), []byte("ippDomain"), *curve)
require.NoError(t, err)
v1 := curve.Scalar.Random(crand.Reader)
v2 := curve.Scalar.Random(crand.Reader)
v3 := curve.Scalar.Random(crand.Reader)
v4 := curve.Scalar.Random(crand.Reader)
v := []curves.Scalar{v1, v2, v3, v4}
gamma1 := curve.Scalar.Random(crand.Reader)
gamma2 := curve.Scalar.Random(crand.Reader)
gamma3 := curve.Scalar.Random(crand.Reader)
gamma4 := curve.Scalar.Random(crand.Reader)
gamma := []curves.Scalar{gamma1, gamma2, gamma3, gamma4}
g := curve.Point.Random(crand.Reader)
h := curve.Point.Random(crand.Reader)
u := curve.Point.Random(crand.Reader)
proofGenerators := RangeProofGenerators{
g: g,
h: h,
u: u,
}
transcript := merlin.NewTranscript("test")
proof, err := prover.BatchProve(v, gamma, n, proofGenerators, transcript)
require.NoError(t, err)
proofMarshaled := proof.MarshalBinary()
proofPrime := NewRangeProof(curve)
err = proofPrime.UnmarshalBinary(proofMarshaled)
require.NoError(t, err)
require.True(t, proof.capA.Equal(proofPrime.capA))
require.True(t, proof.capS.Equal(proofPrime.capS))
require.True(t, proof.capT1.Equal(proofPrime.capT1))
require.True(t, proof.capT2.Equal(proofPrime.capT2))
require.Zero(t, proof.taux.Cmp(proofPrime.taux))
require.Zero(t, proof.mu.Cmp(proofPrime.mu))
require.Zero(t, proof.tHat.Cmp(proofPrime.tHat))
}
+91
View File
@@ -0,0 +1,91 @@
package bulletproof
import (
"github.com/gtank/merlin"
"github.com/pkg/errors"
"github.com/onsonr/sonr/pkg/crypto/core/curves"
)
// VerifyBatched verifies a given batched range proof.
// It takes in a list of commitments to the secret values as capV instead of a single commitment to a single point
// when compared to the unbatched single range proof case.
func (verifier *RangeVerifier) VerifyBatched(proof *RangeProof, capV []curves.Point, proofGenerators RangeProofGenerators, n int, transcript *merlin.Transcript) (bool, error) {
// Define nm as the total bits required for secrets, calculated as number of secrets * n
m := len(capV)
nm := n * m
// nm must be less than the number of generators generated
if nm > len(verifier.generators.G) {
return false, errors.New("ipp vector length must be less than maxVectorLength")
}
// In case where len(a) is less than number of generators precomputed by prover, trim to length
proofG := verifier.generators.G[0:nm]
proofH := verifier.generators.H[0:nm]
// Calc y,z,x from Fiat Shamir heuristic
y, z, err := calcyzBatched(capV, proof.capA, proof.capS, transcript, verifier.curve)
if err != nil {
return false, errors.Wrap(err, "rangeproof verify")
}
x, err := calcx(proof.capT1, proof.capT2, transcript, verifier.curve)
if err != nil {
return false, errors.Wrap(err, "rangeproof verify")
}
wBytes := transcript.ExtractBytes([]byte("getw"), 64)
w, err := verifier.curve.NewScalar().SetBytesWide(wBytes)
if err != nil {
return false, errors.Wrap(err, "rangeproof prove")
}
// Calc delta(y,z), redefined for batched case on pg21
deltayzBatched, err := deltayzBatched(y, z, n, m, verifier.curve)
if err != nil {
return false, errors.Wrap(err, "rangeproof verify")
}
// Check tHat: L65, pg20
// See equation 72 on pg21
tHatIsValid := verifier.checktHatBatched(proof, capV, proofGenerators.g, proofGenerators.h, deltayzBatched, x, z, m)
if !tHatIsValid {
return false, errors.New("rangeproof verify tHat is invalid")
}
// Verify IPP
hPrime, err := gethPrime(proofH, y, verifier.curve)
if err != nil {
return false, errors.Wrap(err, "rangeproof verify")
}
capPhmu := getPhmuBatched(proofG, hPrime, proofGenerators.h, proof.capA, proof.capS, x, y, z, proof.mu, n, m, verifier.curve)
ippVerified, err := verifier.ippVerifier.VerifyFromRangeProof(proofG, hPrime, capPhmu, proofGenerators.u.Mul(w), proof.tHat, proof.ipp, transcript)
if err != nil {
return false, errors.Wrap(err, "rangeproof verify")
}
return ippVerified, nil
}
// L65, pg20.
func (verifier *RangeVerifier) checktHatBatched(proof *RangeProof, capV []curves.Point, g, h curves.Point, deltayz, x, z curves.Scalar, m int) bool {
// g^tHat * h^tau_x
gtHat := g.Mul(proof.tHat)
htaux := h.Mul(proof.taux)
lhs := gtHat.Add(htaux)
// V^z^2 * g^delta(y,z) * Tau_1^x * Tau_2^x^2
// g^delta(y,z) * V^(z^2*z^m) * Tau_1^x * Tau_2^x^2
zm := getknVector(z, m, verifier.curve)
zsquarezm := multiplyScalarToScalarVector(z.Square(), zm)
capVzsquaretwom := verifier.curve.Point.SumOfProducts(capV, zsquarezm)
gdeltayz := g.Mul(deltayz)
capTau1x := proof.capT1.Mul(x)
capTau2xsquare := proof.capT2.Mul(x.Square())
rhs := capVzsquaretwom.Add(gdeltayz).Add(capTau1x).Add(capTau2xsquare)
// Compare lhs =? rhs
return lhs.Equal(rhs)
}
+148
View File
@@ -0,0 +1,148 @@
package bulletproof
import (
crand "crypto/rand"
"testing"
"github.com/gtank/merlin"
"github.com/stretchr/testify/require"
"github.com/onsonr/sonr/pkg/crypto/core/curves"
)
func TestRangeBatchVerifyHappyPath(t *testing.T) {
curve := curves.ED25519()
n := 256
prover, err := NewRangeProver(n*4, []byte("rangeDomain"), []byte("ippDomain"), *curve)
require.NoError(t, err)
v1 := curve.Scalar.Random(crand.Reader)
v2 := curve.Scalar.Random(crand.Reader)
v3 := curve.Scalar.Random(crand.Reader)
v4 := curve.Scalar.Random(crand.Reader)
v := []curves.Scalar{v1, v2, v3, v4}
gamma1 := curve.Scalar.Random(crand.Reader)
gamma2 := curve.Scalar.Random(crand.Reader)
gamma3 := curve.Scalar.Random(crand.Reader)
gamma4 := curve.Scalar.Random(crand.Reader)
gamma := []curves.Scalar{gamma1, gamma2, gamma3, gamma4}
g := curve.Point.Random(crand.Reader)
h := curve.Point.Random(crand.Reader)
u := curve.Point.Random(crand.Reader)
proofGenerators := RangeProofGenerators{
g: g,
h: h,
u: u,
}
transcript := merlin.NewTranscript("test")
proof, err := prover.BatchProve(v, gamma, n, proofGenerators, transcript)
require.NoError(t, err)
verifier, err := NewRangeVerifier(n*4, []byte("rangeDomain"), []byte("ippDomain"), *curve)
require.NoError(t, err)
transcriptVerifier := merlin.NewTranscript("test")
capV := getcapVBatched(v, gamma, g, h)
verified, err := verifier.VerifyBatched(proof, capV, proofGenerators, n, transcriptVerifier)
require.NoError(t, err)
require.True(t, verified)
}
func TestRangeBatchVerifyNotInRange(t *testing.T) {
curve := curves.ED25519()
n := 2
prover, err := NewRangeProver(n*4, []byte("rangeDomain"), []byte("ippDomain"), *curve)
require.NoError(t, err)
v1 := curve.Scalar.One()
v2 := curve.Scalar.Random(crand.Reader)
v3 := curve.Scalar.Random(crand.Reader)
v4 := curve.Scalar.Random(crand.Reader)
v := []curves.Scalar{v1, v2, v3, v4}
gamma1 := curve.Scalar.Random(crand.Reader)
gamma2 := curve.Scalar.Random(crand.Reader)
gamma3 := curve.Scalar.Random(crand.Reader)
gamma4 := curve.Scalar.Random(crand.Reader)
gamma := []curves.Scalar{gamma1, gamma2, gamma3, gamma4}
g := curve.Point.Random(crand.Reader)
h := curve.Point.Random(crand.Reader)
u := curve.Point.Random(crand.Reader)
proofGenerators := RangeProofGenerators{
g: g,
h: h,
u: u,
}
transcript := merlin.NewTranscript("test")
_, err = prover.BatchProve(v, gamma, n, proofGenerators, transcript)
require.Error(t, err)
}
func TestRangeBatchVerifyNonRandom(t *testing.T) {
curve := curves.ED25519()
n := 2
prover, err := NewRangeProver(n*4, []byte("rangeDomain"), []byte("ippDomain"), *curve)
require.NoError(t, err)
v1 := curve.Scalar.One()
v2 := curve.Scalar.One()
v3 := curve.Scalar.One()
v4 := curve.Scalar.One()
v := []curves.Scalar{v1, v2, v3, v4}
gamma1 := curve.Scalar.Random(crand.Reader)
gamma2 := curve.Scalar.Random(crand.Reader)
gamma3 := curve.Scalar.Random(crand.Reader)
gamma4 := curve.Scalar.Random(crand.Reader)
gamma := []curves.Scalar{gamma1, gamma2, gamma3, gamma4}
g := curve.Point.Random(crand.Reader)
h := curve.Point.Random(crand.Reader)
u := curve.Point.Random(crand.Reader)
proofGenerators := RangeProofGenerators{
g: g,
h: h,
u: u,
}
transcript := merlin.NewTranscript("test")
proof, err := prover.BatchProve(v, gamma, n, proofGenerators, transcript)
require.NoError(t, err)
verifier, err := NewRangeVerifier(n*4, []byte("rangeDomain"), []byte("ippDomain"), *curve)
require.NoError(t, err)
transcriptVerifier := merlin.NewTranscript("test")
capV := getcapVBatched(v, gamma, g, h)
verified, err := verifier.VerifyBatched(proof, capV, proofGenerators, n, transcriptVerifier)
require.NoError(t, err)
require.True(t, verified)
}
func TestRangeBatchVerifyInvalid(t *testing.T) {
curve := curves.ED25519()
n := 2
prover, err := NewRangeProver(n*4, []byte("rangeDomain"), []byte("ippDomain"), *curve)
require.NoError(t, err)
v1 := curve.Scalar.One()
v2 := curve.Scalar.One()
v3 := curve.Scalar.One()
v4 := curve.Scalar.One()
v := []curves.Scalar{v1, v2, v3, v4}
gamma1 := curve.Scalar.Random(crand.Reader)
gamma2 := curve.Scalar.Random(crand.Reader)
gamma3 := curve.Scalar.Random(crand.Reader)
gamma4 := curve.Scalar.Random(crand.Reader)
gamma := []curves.Scalar{gamma1, gamma2, gamma3, gamma4}
g := curve.Point.Random(crand.Reader)
h := curve.Point.Random(crand.Reader)
u := curve.Point.Random(crand.Reader)
proofGenerators := RangeProofGenerators{
g: g,
h: h,
u: u,
}
transcript := merlin.NewTranscript("test")
proof, err := prover.BatchProve(v, gamma, n, proofGenerators, transcript)
require.NoError(t, err)
verifier, err := NewRangeVerifier(n*4, []byte("rangeDomain"), []byte("ippDomain"), *curve)
require.NoError(t, err)
transcriptVerifier := merlin.NewTranscript("test")
capV := getcapVBatched(v, gamma, g, h)
capV[0] = curve.Point.Random(crand.Reader)
verified, err := verifier.VerifyBatched(proof, capV, proofGenerators, n, transcriptVerifier)
require.Error(t, err)
require.False(t, verified)
}
+476
View File
@@ -0,0 +1,476 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
// Package bulletproof implements the zero knowledge protocol bulletproofs as defined in https://eprint.iacr.org/2017/1066.pdf
package bulletproof
import (
crand "crypto/rand"
"math/big"
"github.com/gtank/merlin"
"github.com/pkg/errors"
"github.com/onsonr/sonr/pkg/crypto/core/curves"
)
// RangeProver is the struct used to create RangeProofs
// It specifies which curve to use and holds precomputed generators
// See NewRangeProver() for prover initialization.
type RangeProver struct {
curve curves.Curve
generators *ippGenerators
ippProver *InnerProductProver
}
// RangeProof is the struct used to hold a range proof
// capA is a commitment to a_L and a_R using randomness alpha
// capS is a commitment to s_L and s_R using randomness rho
// capTau1,2 are commitments to t1,t2 respectively using randomness tau_1,2
// tHat represents t(X) as defined on page 19
// taux is the blinding factor for tHat
// ipp is the inner product proof used for compacting the transfer of l,r (See 4.2 on pg20).
type RangeProof struct {
capA, capS, capT1, capT2 curves.Point
taux, mu, tHat curves.Scalar
ipp *InnerProductProof
curve *curves.Curve
}
type RangeProofGenerators struct {
g, h, u curves.Point
}
// NewRangeProver initializes a new prover
// It uses the specified domain to generate generators for vectors of at most maxVectorLength
// A prover can be used to construct range proofs for vectors of length less than or equal to maxVectorLength
// A prover is defined by an explicit curve.
func NewRangeProver(maxVectorLength int, rangeDomain, ippDomain []byte, curve curves.Curve) (*RangeProver, error) {
generators, err := getGeneratorPoints(maxVectorLength, rangeDomain, curve)
if err != nil {
return nil, errors.Wrap(err, "range NewRangeProver")
}
ippProver, err := NewInnerProductProver(maxVectorLength, ippDomain, curve)
if err != nil {
return nil, errors.Wrap(err, "range NewRangeProver")
}
return &RangeProver{curve: curve, generators: generators, ippProver: ippProver}, nil
}
// NewRangeProof initializes a new RangeProof for a specified curve
// This should be used in tandem with UnmarshalBinary() to convert a marshaled proof into the struct.
func NewRangeProof(curve *curves.Curve) *RangeProof {
out := RangeProof{
capA: nil,
capS: nil,
capT1: nil,
capT2: nil,
taux: nil,
mu: nil,
tHat: nil,
ipp: NewInnerProductProof(curve),
curve: curve,
}
return &out
}
// Prove uses the range prover to prove that some value v is within the range [0, 2^n]
// It implements the protocol defined on pgs 19,20 in https://eprint.iacr.org/2017/1066.pdf
// v is the value of which to prove the range
// n is the power that specifies the upper bound of the range, ie. 2^n
// gamma is a scalar used for as a blinding factor
// g, h, u are unique points used as generators for the blinding factor
// transcript is a merlin transcript to be used for the fiat shamir heuristic.
func (prover *RangeProver) Prove(v, gamma curves.Scalar, n int, proofGenerators RangeProofGenerators, transcript *merlin.Transcript) (*RangeProof, error) {
// n must be less than or equal to the number of generators generated
if n > len(prover.generators.G) {
return nil, errors.New("ipp vector length must be less than or equal to maxVectorLength")
}
// In case where len(a) is less than number of generators precomputed by prover, trim to length
proofG := prover.generators.G[0:n]
proofH := prover.generators.H[0:n]
// Check that v is in range [0, 2^n]
if bigZero := big.NewInt(0); v.BigInt().Cmp(bigZero) == -1 {
return nil, errors.New("v is less than 0")
}
bigTwo := big.NewInt(2)
if n < 0 {
return nil, errors.New("n cannot be less than 0")
}
bigN := big.NewInt(int64(n))
var bigTwoToN big.Int
bigTwoToN.Exp(bigTwo, bigN, nil)
if v.BigInt().Cmp(&bigTwoToN) == 1 {
return nil, errors.New("v is greater than 2^n")
}
// L40 on pg19
aL, err := getaL(v, n, prover.curve)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
onen := get1nVector(n, prover.curve)
// L41 on pg19
aR, err := subtractPairwiseScalarVectors(aL, onen)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
alpha := prover.curve.Scalar.Random(crand.Reader)
// Calc A (L44, pg19)
halpha := proofGenerators.h.Mul(alpha)
gaL := prover.curve.Point.SumOfProducts(proofG, aL)
haR := prover.curve.Point.SumOfProducts(proofH, aR)
capA := halpha.Add(gaL).Add(haR)
// L45, 46, pg19
sL := getBlindingVector(n, prover.curve)
sR := getBlindingVector(n, prover.curve)
rho := prover.curve.Scalar.Random(crand.Reader)
// Calc S (L47, pg19)
hrho := proofGenerators.h.Mul(rho)
gsL := prover.curve.Point.SumOfProducts(proofG, sL)
hsR := prover.curve.Point.SumOfProducts(proofH, sR)
capS := hrho.Add(gsL).Add(hsR)
// Fiat Shamir for y,z (L49, pg19)
capV := getcapV(v, gamma, proofGenerators.g, proofGenerators.h)
y, z, err := calcyz(capV, capA, capS, transcript, prover.curve)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
// Calc t_1, t_2
// See the l(X), r(X), t(X) equations on pg 19
// Use l(X)'s and r(X)'s constant and linear terms to derive t_1 and t_2
// (a_l - z*1^n)
zonen := multiplyScalarToScalarVector(z, onen)
constantTerml, err := subtractPairwiseScalarVectors(aL, zonen)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
linearTerml := sL
// z^2 * 2^N
twoN := get2nVector(n, prover.curve)
zSquareTwon := multiplyScalarToScalarVector(z.Square(), twoN)
// a_r + z*1^n
aRPluszonen, err := addPairwiseScalarVectors(aR, zonen)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
yn := getknVector(y, n, prover.curve)
hadamard, err := multiplyPairwiseScalarVectors(yn, aRPluszonen)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
constantTermr, err := addPairwiseScalarVectors(hadamard, zSquareTwon)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
linearTermr, err := multiplyPairwiseScalarVectors(yn, sR)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
// t_1 (as the linear coefficient) is the sum of the dot products of l(X)'s linear term dot r(X)'s constant term
// and r(X)'s linear term dot l(X)'s constant term
t1FirstTerm, err := innerProduct(linearTerml, constantTermr)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
t1SecondTerm, err := innerProduct(linearTermr, constantTerml)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
t1 := t1FirstTerm.Add(t1SecondTerm)
// t_2 (as the quadratic coefficient) is the dot product of l(X)'s and r(X)'s linear terms
t2, err := innerProduct(linearTerml, linearTermr)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
// L52, pg20
tau1 := prover.curve.Scalar.Random(crand.Reader)
tau2 := prover.curve.Scalar.Random(crand.Reader)
// T_1, T_2 (L53, pg20)
capT1 := proofGenerators.g.Mul(t1).Add(proofGenerators.h.Mul(tau1))
capT2 := proofGenerators.g.Mul(t2).Add(proofGenerators.h.Mul(tau2))
// Fiat shamir for x (L55, pg20)
x, err := calcx(capT1, capT2, transcript, prover.curve)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
// Calc l (L58, pg20)
// Instead of using the expression in the line, evaluate l() at x
sLx := multiplyScalarToScalarVector(x, linearTerml)
l, err := addPairwiseScalarVectors(constantTerml, sLx)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
// Calc r (L59, pg20)
// Instead of using the expression in the line, evaluate r() at x
ynsRx := multiplyScalarToScalarVector(x, linearTermr)
r, err := addPairwiseScalarVectors(constantTermr, ynsRx)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
// Calc t hat (L60, pg20)
// For efficiency, instead of calculating the dot product, evaluate t() at x
deltayz, err := deltayz(y, z, n, prover.curve)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
t0 := v.Mul(z.Square()).Add(deltayz)
tLinear := t1.Mul(x)
tQuadratic := t2.Mul(x.Square())
tHat := t0.Add(tLinear).Add(tQuadratic)
// Calc tau_x (L61, pg20)
tau2xsquare := tau2.Mul(x.Square())
tau1x := tau1.Mul(x)
zsquaregamma := z.Square().Mul(gamma)
taux := tau2xsquare.Add(tau1x).Add(zsquaregamma)
// Calc mu (L62, pg20)
mu := alpha.Add(rho.Mul(x))
// Calc IPP (See section 4.2)
hPrime, err := gethPrime(proofH, y, prover.curve)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
capPhmu, err := getPhmu(proofG, hPrime, proofGenerators.h, capA, capS, x, y, z, mu, n, prover.curve)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
wBytes := transcript.ExtractBytes([]byte("getw"), 64)
w, err := prover.curve.NewScalar().SetBytesWide(wBytes)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
ipp, err := prover.ippProver.rangeToIPP(proofG, hPrime, l, r, tHat, capPhmu, proofGenerators.u.Mul(w), transcript)
if err != nil {
return nil, errors.Wrap(err, "rangeproof prove")
}
out := &RangeProof{
capA: capA,
capS: capS,
capT1: capT1,
capT2: capT2,
taux: taux,
mu: mu,
tHat: tHat,
ipp: ipp,
curve: &prover.curve,
}
return out, nil
}
// MarshalBinary takes a range proof and marshals into bytes.
func (proof *RangeProof) MarshalBinary() []byte {
var out []byte
out = append(out, proof.capA.ToAffineCompressed()...)
out = append(out, proof.capS.ToAffineCompressed()...)
out = append(out, proof.capT1.ToAffineCompressed()...)
out = append(out, proof.capT2.ToAffineCompressed()...)
out = append(out, proof.taux.Bytes()...)
out = append(out, proof.mu.Bytes()...)
out = append(out, proof.tHat.Bytes()...)
out = append(out, proof.ipp.MarshalBinary()...)
return out
}
// UnmarshalBinary takes bytes of a marshaled proof and writes them into a range proof
// The range proof used should be from the output of NewRangeProof().
func (proof *RangeProof) UnmarshalBinary(data []byte) error {
scalarLen := len(proof.curve.NewScalar().Bytes())
pointLen := len(proof.curve.NewGeneratorPoint().ToAffineCompressed())
ptr := 0
// Get points
capA, err := proof.curve.Point.FromAffineCompressed(data[ptr : ptr+pointLen])
if err != nil {
return errors.New("rangeProof UnmarshalBinary FromAffineCompressed")
}
proof.capA = capA
ptr += pointLen
capS, err := proof.curve.Point.FromAffineCompressed(data[ptr : ptr+pointLen])
if err != nil {
return errors.New("rangeProof UnmarshalBinary FromAffineCompressed")
}
proof.capS = capS
ptr += pointLen
capT1, err := proof.curve.Point.FromAffineCompressed(data[ptr : ptr+pointLen])
if err != nil {
return errors.New("rangeProof UnmarshalBinary FromAffineCompressed")
}
proof.capT1 = capT1
ptr += pointLen
capT2, err := proof.curve.Point.FromAffineCompressed(data[ptr : ptr+pointLen])
if err != nil {
return errors.New("rangeProof UnmarshalBinary FromAffineCompressed")
}
proof.capT2 = capT2
ptr += pointLen
// Get scalars
taux, err := proof.curve.NewScalar().SetBytes(data[ptr : ptr+scalarLen])
if err != nil {
return errors.New("rangeProof UnmarshalBinary SetBytes")
}
proof.taux = taux
ptr += scalarLen
mu, err := proof.curve.NewScalar().SetBytes(data[ptr : ptr+scalarLen])
if err != nil {
return errors.New("rangeProof UnmarshalBinary SetBytes")
}
proof.mu = mu
ptr += scalarLen
tHat, err := proof.curve.NewScalar().SetBytes(data[ptr : ptr+scalarLen])
if err != nil {
return errors.New("rangeProof UnmarshalBinary SetBytes")
}
proof.tHat = tHat
ptr += scalarLen
// Get IPP
err = proof.ipp.UnmarshalBinary(data[ptr:])
if err != nil {
return errors.New("rangeProof UnmarshalBinary")
}
return nil
}
// checkRange validates whether some scalar v is within the range [0, 2^n - 1]
// It will return an error if v is less than 0 or greater than 2^n - 1
// Otherwise it will return nil.
func checkRange(v curves.Scalar, n int) error {
bigOne := big.NewInt(1)
if n < 0 {
return errors.New("n cannot be less than 0")
}
var bigTwoToN big.Int
bigTwoToN.Lsh(bigOne, uint(n))
if v.BigInt().Cmp(&bigTwoToN) == 1 {
return errors.New("v is greater than 2^n")
}
return nil
}
// getBlindingVector returns a vector of scalars used as blinding factors for commitments.
func getBlindingVector(length int, curve curves.Curve) []curves.Scalar {
vec := make([]curves.Scalar, length)
for i := 0; i < length; i++ {
vec[i] = curve.Scalar.Random(crand.Reader)
}
return vec
}
// getcapV returns a commitment to v using blinding factor gamma.
func getcapV(v, gamma curves.Scalar, g, h curves.Point) curves.Point {
return h.Mul(gamma).Add(g.Mul(v))
}
// getaL obtains the bit vector representation of v
// See the a_L definition towards the bottom of pg 17 of https://eprint.iacr.org/2017/1066.pdf
func getaL(v curves.Scalar, n int, curve curves.Curve) ([]curves.Scalar, error) {
var err error
vBytes := v.Bytes()
zero := curve.Scalar.Zero()
one := curve.Scalar.One()
aL := make([]curves.Scalar, n)
for j := 0; j < len(aL); j++ {
aL[j] = zero
}
for i := 0; i < n; i++ {
ithBit := vBytes[i>>3] >> (i & 0x07) & 0x01
aL[i], err = cmoveScalar(zero, one, int(ithBit), curve)
if err != nil {
return nil, errors.Wrap(err, "getaL")
}
}
return aL, nil
}
// cmoveScalar provides a constant time operation that returns x if which is 0 and returns y if which is 1.
func cmoveScalar(x, y curves.Scalar, which int, curve curves.Curve) (curves.Scalar, error) {
if which != 0 && which != 1 {
return nil, errors.New("cmoveScalar which must be 0 or 1")
}
mask := -byte(which)
xBytes := x.Bytes()
yBytes := y.Bytes()
for i, xByte := range xBytes {
xBytes[i] ^= (xByte ^ yBytes[i]) & mask
}
out, err := curve.NewScalar().SetBytes(xBytes)
if err != nil {
return nil, errors.Wrap(err, "cmoveScalar SetBytes")
}
return out, nil
}
// calcyz uses a merlin transcript for Fiat Shamir
// It takes the current state of the transcript and appends the newly calculated capA and capS values
// Two new scalars are then read from the transcript
// See section 4.4 pg22 of https://eprint.iacr.org/2017/1066.pdf
func calcyz(capV, capA, capS curves.Point, transcript *merlin.Transcript, curve curves.Curve) (curves.Scalar, curves.Scalar, error) {
// Add the A,S values to transcript
transcript.AppendMessage([]byte("addV"), capV.ToAffineUncompressed())
transcript.AppendMessage([]byte("addcapA"), capA.ToAffineUncompressed())
transcript.AppendMessage([]byte("addcapS"), capS.ToAffineUncompressed())
// Read 64 bytes twice from, set to scalar for y and z
yBytes := transcript.ExtractBytes([]byte("gety"), 64)
y, err := curve.NewScalar().SetBytesWide(yBytes)
if err != nil {
return nil, nil, errors.Wrap(err, "calcyz NewScalar SetBytesWide")
}
zBytes := transcript.ExtractBytes([]byte("getz"), 64)
z, err := curve.NewScalar().SetBytesWide(zBytes)
if err != nil {
return nil, nil, errors.Wrap(err, "calcyz NewScalar SetBytesWide")
}
return y, z, nil
}
// calcx uses a merlin transcript for Fiat Shamir
// It takes the current state of the transcript and appends the newly calculated capT1 and capT2 values
// A new scalar is then read from the transcript
// See section 4.4 pg22 of https://eprint.iacr.org/2017/1066.pdf
func calcx(capT1, capT2 curves.Point, transcript *merlin.Transcript, curve curves.Curve) (curves.Scalar, error) {
// Add the Tau1,2 values to transcript
transcript.AppendMessage([]byte("addcapT1"), capT1.ToAffineUncompressed())
transcript.AppendMessage([]byte("addcapT2"), capT2.ToAffineUncompressed())
// Read 64 bytes from, set to scalar
outBytes := transcript.ExtractBytes([]byte("getx"), 64)
x, err := curve.NewScalar().SetBytesWide(outBytes)
if err != nil {
return nil, errors.Wrap(err, "calcx NewScalar SetBytesWide")
}
return x, nil
}
+86
View File
@@ -0,0 +1,86 @@
package bulletproof
import (
crand "crypto/rand"
"testing"
"github.com/gtank/merlin"
"github.com/stretchr/testify/require"
"github.com/onsonr/sonr/pkg/crypto/core/curves"
)
func TestRangeProverHappyPath(t *testing.T) {
curve := curves.ED25519()
n := 256
prover, err := NewRangeProver(n, []byte("rangeDomain"), []byte("ippDomain"), *curve)
require.NoError(t, err)
v := curve.Scalar.Random(crand.Reader)
gamma := curve.Scalar.Random(crand.Reader)
g := curve.Point.Random(crand.Reader)
h := curve.Point.Random(crand.Reader)
u := curve.Point.Random(crand.Reader)
proofGenerators := RangeProofGenerators{
g: g,
h: h,
u: u,
}
transcript := merlin.NewTranscript("test")
proof, err := prover.Prove(v, gamma, n, proofGenerators, transcript)
require.NoError(t, err)
require.NotNil(t, proof)
require.Equal(t, 8, len(proof.ipp.capLs))
require.Equal(t, 8, len(proof.ipp.capRs))
}
func TestGetaL(t *testing.T) {
curve := curves.ED25519()
v := curve.Scalar.Random(crand.Reader)
aL, err := getaL(v, 256, *curve)
require.NoError(t, err)
twoN := get2nVector(256, *curve)
product, err := innerProduct(aL, twoN)
require.NoError(t, err)
require.Zero(t, product.Cmp(v))
}
func TestCmove(t *testing.T) {
curve := curves.ED25519()
two := curve.Scalar.One().Double()
four := two.Double()
out, err := cmoveScalar(two, four, 1, *curve)
require.NoError(t, err)
require.Zero(t, out.Cmp(four))
}
func TestRangeProverMarshal(t *testing.T) {
curve := curves.ED25519()
n := 256
prover, err := NewRangeProver(n, []byte("rangeDomain"), []byte("ippDomain"), *curve)
require.NoError(t, err)
v := curve.Scalar.Random(crand.Reader)
gamma := curve.Scalar.Random(crand.Reader)
g := curve.Point.Random(crand.Reader)
h := curve.Point.Random(crand.Reader)
u := curve.Point.Random(crand.Reader)
proofGenerators := RangeProofGenerators{
g: g,
h: h,
u: u,
}
transcript := merlin.NewTranscript("test")
proof, err := prover.Prove(v, gamma, n, proofGenerators, transcript)
require.NoError(t, err)
proofMarshaled := proof.MarshalBinary()
proofPrime := NewRangeProof(curve)
err = proofPrime.UnmarshalBinary(proofMarshaled)
require.NoError(t, err)
require.True(t, proof.capA.Equal(proofPrime.capA))
require.True(t, proof.capS.Equal(proofPrime.capS))
require.True(t, proof.capT1.Equal(proofPrime.capT1))
require.True(t, proof.capT2.Equal(proofPrime.capT2))
require.Zero(t, proof.taux.Cmp(proofPrime.taux))
require.Zero(t, proof.mu.Cmp(proofPrime.mu))
require.Zero(t, proof.tHat.Cmp(proofPrime.tHat))
}
+187
View File
@@ -0,0 +1,187 @@
package bulletproof
import (
"github.com/gtank/merlin"
"github.com/pkg/errors"
"github.com/onsonr/sonr/pkg/crypto/core/curves"
)
// RangeVerifier is the struct used to verify RangeProofs
// It specifies which curve to use and holds precomputed generators
// See NewRangeVerifier() for verifier initialization.
type RangeVerifier struct {
curve curves.Curve
generators *ippGenerators
ippVerifier *InnerProductVerifier
}
// NewRangeVerifier initializes a new verifier
// It uses the specified domain to generate generators for vectors of at most maxVectorLength
// A verifier can be used to verify range proofs for vectors of length less than or equal to maxVectorLength
// A verifier is defined by an explicit curve.
func NewRangeVerifier(maxVectorLength int, rangeDomain, ippDomain []byte, curve curves.Curve) (*RangeVerifier, error) {
generators, err := getGeneratorPoints(maxVectorLength, rangeDomain, curve)
if err != nil {
return nil, errors.Wrap(err, "range NewRangeProver")
}
ippVerifier, err := NewInnerProductVerifier(maxVectorLength, ippDomain, curve)
if err != nil {
return nil, errors.Wrap(err, "range NewRangeProver")
}
return &RangeVerifier{curve: curve, generators: generators, ippVerifier: ippVerifier}, nil
}
// Verify verifies the given range proof inputs
// It implements the checking of L65 on pg 20
// It also verifies the dot product of <l,r> using the inner product proof\
// capV is a commitment to v using blinding factor gamma
// n is the power that specifies the upper bound of the range, ie. 2^n
// g, h, u are unique points used as generators for the blinding factor
// transcript is a merlin transcript to be used for the fiat shamir heuristic.
func (verifier *RangeVerifier) Verify(proof *RangeProof, capV curves.Point, proofGenerators RangeProofGenerators, n int, transcript *merlin.Transcript) (bool, error) {
// Length of vectors must be less than the number of generators generated
if n > len(verifier.generators.G) {
return false, errors.New("ipp vector length must be less than maxVectorLength")
}
// In case where len(a) is less than number of generators precomputed by prover, trim to length
proofG := verifier.generators.G[0:n]
proofH := verifier.generators.H[0:n]
// Calc y,z,x from Fiat Shamir heuristic
y, z, err := calcyz(capV, proof.capA, proof.capS, transcript, verifier.curve)
if err != nil {
return false, errors.Wrap(err, "rangeproof verify")
}
x, err := calcx(proof.capT1, proof.capT2, transcript, verifier.curve)
if err != nil {
return false, errors.Wrap(err, "rangeproof verify")
}
wBytes := transcript.ExtractBytes([]byte("getw"), 64)
w, err := verifier.curve.NewScalar().SetBytesWide(wBytes)
if err != nil {
return false, errors.Wrap(err, "rangeproof prove")
}
// Calc delta(y,z)
deltayz, err := deltayz(y, z, n, verifier.curve)
if err != nil {
return false, errors.Wrap(err, "rangeproof verify")
}
// Check tHat: L65, pg20
tHatIsValid := verifier.checktHat(proof, capV, proofGenerators.g, proofGenerators.h, deltayz, x, z)
if !tHatIsValid {
return false, errors.New("rangeproof verify tHat is invalid")
}
// Verify IPP
hPrime, err := gethPrime(proofH, y, verifier.curve)
if err != nil {
return false, errors.Wrap(err, "rangeproof verify")
}
capPhmu, err := getPhmu(proofG, hPrime, proofGenerators.h, proof.capA, proof.capS, x, y, z, proof.mu, n, verifier.curve)
if err != nil {
return false, errors.Wrap(err, "rangeproof verify")
}
ippVerified, err := verifier.ippVerifier.VerifyFromRangeProof(proofG, hPrime, capPhmu, proofGenerators.u.Mul(w), proof.tHat, proof.ipp, transcript)
if err != nil {
return false, errors.Wrap(err, "rangeproof verify")
}
return ippVerified, nil
}
// L65, pg20.
func (*RangeVerifier) checktHat(proof *RangeProof, capV, g, h curves.Point, deltayz, x, z curves.Scalar) bool {
// g^tHat * h^tau_x
gtHat := g.Mul(proof.tHat)
htaux := h.Mul(proof.taux)
lhs := gtHat.Add(htaux)
// V^z^2 * g^delta(y,z) * Tau_1^x * Tau_2^x^2
capVzsquare := capV.Mul(z.Square())
gdeltayz := g.Mul(deltayz)
capTau1x := proof.capT1.Mul(x)
capTau2xsquare := proof.capT2.Mul(x.Square())
rhs := capVzsquare.Add(gdeltayz).Add(capTau1x).Add(capTau2xsquare)
// Compare lhs =? rhs
return lhs.Equal(rhs)
}
// gethPrime calculates new h prime generators as defined in L64 on pg20.
func gethPrime(h []curves.Point, y curves.Scalar, curve curves.Curve) ([]curves.Point, error) {
hPrime := make([]curves.Point, len(h))
yInv, err := y.Invert()
yInvn := getknVector(yInv, len(h), curve)
if err != nil {
return nil, errors.Wrap(err, "gethPrime")
}
for i, hElem := range h {
hPrime[i] = hElem.Mul(yInvn[i])
}
return hPrime, nil
}
// Obtain P used for IPP verification
// See L67 on pg20
// Note P on L66 includes blinding factor hmu, this method removes that factor.
func getPhmu(proofG, proofHPrime []curves.Point, h, capA, capS curves.Point, x, y, z, mu curves.Scalar, n int, curve curves.Curve) (curves.Point, error) {
// h'^(z*y^n + z^2*2^n)
zyn := multiplyScalarToScalarVector(z, getknVector(y, n, curve))
zsquaretwon := multiplyScalarToScalarVector(z.Square(), get2nVector(n, curve))
elemLastExponent, err := addPairwiseScalarVectors(zyn, zsquaretwon)
if err != nil {
return nil, errors.Wrap(err, "getPhmu")
}
lastElem := curve.Point.SumOfProducts(proofHPrime, elemLastExponent)
// S^x
capSx := capS.Mul(x)
// g^-z --> -z*<1,g>
onen := get1nVector(n, curve)
zNeg := z.Neg()
zinvonen := multiplyScalarToScalarVector(zNeg, onen)
zgdotonen := curve.Point.SumOfProducts(proofG, zinvonen)
// L66 on pg20
P := capA.Add(capSx).Add(zgdotonen).Add(lastElem)
hmu := h.Mul(mu)
Phmu := P.Sub(hmu)
return Phmu, nil
}
// Delta function for delta(y,z), See (39) on pg18.
func deltayz(y, z curves.Scalar, n int, curve curves.Curve) (curves.Scalar, error) {
// z - z^2
zMinuszsquare := z.Sub(z.Square())
// 1^n
onen := get1nVector(n, curve)
// <1^n, y^n>
onendotyn, err := innerProduct(onen, getknVector(y, n, curve))
if err != nil {
return nil, errors.Wrap(err, "deltayz")
}
// (z - z^2)*<1^n, y^n>
termFirst := zMinuszsquare.Mul(onendotyn)
// <1^n, 2^n>
onendottwon, err := innerProduct(onen, get2nVector(n, curve))
if err != nil {
return nil, errors.Wrap(err, "deltayz")
}
// z^3*<1^n, 2^n>
termSecond := z.Cube().Mul(onendottwon)
// (z - z^2)*<1^n, y^n> - z^3*<1^n, 2^n>
out := termFirst.Sub(termSecond)
return out, nil
}
+87
View File
@@ -0,0 +1,87 @@
package bulletproof
import (
crand "crypto/rand"
"testing"
"github.com/gtank/merlin"
"github.com/stretchr/testify/require"
"github.com/onsonr/sonr/pkg/crypto/core/curves"
)
func TestRangeVerifyHappyPath(t *testing.T) {
curve := curves.ED25519()
n := 256
prover, err := NewRangeProver(n, []byte("rangeDomain"), []byte("ippDomain"), *curve)
require.NoError(t, err)
v := curve.Scalar.Random(crand.Reader)
gamma := curve.Scalar.Random(crand.Reader)
g := curve.Point.Random(crand.Reader)
h := curve.Point.Random(crand.Reader)
u := curve.Point.Random(crand.Reader)
proofGenerators := RangeProofGenerators{
g: g,
h: h,
u: u,
}
transcript := merlin.NewTranscript("test")
proof, err := prover.Prove(v, gamma, n, proofGenerators, transcript)
require.NoError(t, err)
verifier, err := NewRangeVerifier(n, []byte("rangeDomain"), []byte("ippDomain"), *curve)
require.NoError(t, err)
transcriptVerifier := merlin.NewTranscript("test")
capV := getcapV(v, gamma, g, h)
verified, err := verifier.Verify(proof, capV, proofGenerators, n, transcriptVerifier)
require.NoError(t, err)
require.True(t, verified)
}
func TestRangeVerifyNotInRange(t *testing.T) {
curve := curves.ED25519()
n := 2
prover, err := NewRangeProver(n, []byte("rangeDomain"), []byte("ippDomain"), *curve)
require.NoError(t, err)
v := curve.Scalar.Random(crand.Reader)
gamma := curve.Scalar.Random(crand.Reader)
g := curve.Point.Random(crand.Reader)
h := curve.Point.Random(crand.Reader)
u := curve.Point.Random(crand.Reader)
proofGenerators := RangeProofGenerators{
g: g,
h: h,
u: u,
}
transcript := merlin.NewTranscript("test")
_, err = prover.Prove(v, gamma, n, proofGenerators, transcript)
require.Error(t, err)
}
func TestRangeVerifyNonRandom(t *testing.T) {
curve := curves.ED25519()
n := 2
prover, err := NewRangeProver(n, []byte("rangeDomain"), []byte("ippDomain"), *curve)
require.NoError(t, err)
v := curve.Scalar.One()
gamma := curve.Scalar.Random(crand.Reader)
g := curve.Point.Random(crand.Reader)
h := curve.Point.Random(crand.Reader)
u := curve.Point.Random(crand.Reader)
proofGenerators := RangeProofGenerators{
g: g,
h: h,
u: u,
}
transcript := merlin.NewTranscript("test")
proof, err := prover.Prove(v, gamma, n, proofGenerators, transcript)
require.NoError(t, err)
verifier, err := NewRangeVerifier(n, []byte("rangeDomain"), []byte("ippDomain"), *curve)
require.NoError(t, err)
transcriptVerifier := merlin.NewTranscript("test")
capV := getcapV(v, gamma, g, h)
verified, err := verifier.Verify(proof, capV, proofGenerators, n, transcriptVerifier)
require.NoError(t, err)
require.True(t, verified)
}
+14
View File
@@ -0,0 +1,14 @@
---
aliases: [README]
tags: []
title: README
linter-yaml-title-alias: README
date created: Wednesday, April 17th 2024, 4:11:40 pm
date modified: Thursday, April 18th 2024, 8:19:25 am
---
## Core Package
The core package contains a set of primitives, including but not limited to various
elliptic curves, hashes, and commitment schemes. These primitives are used internally
and can also be used independently on their own externally.
+115
View File
@@ -0,0 +1,115 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package core
import (
"crypto/hmac"
crand "crypto/rand"
"crypto/sha256"
"crypto/subtle"
"encoding/json"
"fmt"
"hash"
)
// Size of random values and hash outputs are determined by our hash function
const Size = sha256.Size
type (
// Commitment to a given message which can be later revealed.
// This is sent to and held by a verifier until the corresponding
// witness is provided.
Commitment []byte
// Witness is sent to and opened by the verifier. This proves that
// committed message hasn't been altered by later information.
Witness struct {
Msg []byte
r [Size]byte
}
// witnessJSON is used for un/marshaling.
witnessJSON struct {
Msg []byte
R [Size]byte
}
)
// MarshalJSON encodes Witness in JSON
func (w Witness) MarshalJSON() ([]byte, error) {
return json.Marshal(witnessJSON{w.Msg, w.r})
}
// UnmarshalJSON decodes JSON into a Witness struct
func (w *Witness) UnmarshalJSON(data []byte) error {
witness := &witnessJSON{}
err := json.Unmarshal(data, witness)
if err != nil {
return err
}
w.Msg = witness.Msg
w.r = witness.R
return nil
}
// Commit to a given message. Uses SHA256 as the hash function.
func Commit(msg []byte) (Commitment, *Witness, error) {
// Initialize our decommitment
d := Witness{msg, [Size]byte{}}
// Generate a random nonce of the required length
n, err := crand.Read(d.r[:])
// Ensure no errors retrieving nonce
if err != nil {
return nil, nil, err
}
// Ensure we read all the bytes expected
if n != Size {
return nil, nil, fmt.Errorf("failed to read %v bytes from crypto/rand: received %v bytes", Size, n)
}
// Compute the commitment: HMAC(Sha2, msg, key)
c, err := ComputeHMAC(sha256.New, msg, d.r[:])
if err != nil {
return nil, nil, err
}
return c, &d, nil
}
// Open a commitment and return true if the commitment/decommitment pair are valid.
// reference: spec.§2.4: Commitment Scheme
func Open(c Commitment, d Witness) (bool, error) {
// Ensure commitment is well-formed.
if len(c) != Size {
return false, fmt.Errorf("invalid commitment, wrong length. %v != %v", len(c), Size)
}
// Re-compute the commitment: HMAC(Sha2, msg, key)
cʹ, err := ComputeHMAC(sha256.New, d.Msg, d.r[:])
if err != nil {
return false, err
}
return subtle.ConstantTimeCompare(cʹ, c) == 1, nil
}
// ComputeHMAC computes HMAC(hash_fn, msg, key)
// Takes in a hash function to use for HMAC
func ComputeHMAC(f func() hash.Hash, msg []byte, k []byte) ([]byte, error) {
if f == nil {
return nil, fmt.Errorf("hash function cannot be nil")
}
mac := hmac.New(f, k)
w, err := mac.Write(msg)
if w != len(msg) {
return nil, fmt.Errorf("bytes written to hash doesn't match expected: %v != %v", w, len(msg))
} else if err != nil {
return nil, err
}
return mac.Sum(nil), nil
}
+374
View File
@@ -0,0 +1,374 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package core
import (
"bytes"
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
)
// An entry into our test table
type entry struct {
// Input
msg []byte
// Result (actual, not expected)
commit Commitment
decommit *Witness
err error
}
// Test inputs and placeholders for results that will be filled in
// during init()
var testResults = []entry{
{[]byte("This is a test message"), nil, nil, nil},
{[]byte("short msg"), nil, nil, nil},
{[]byte("This input field is intentionally longer than the SHA256 block size to ensure that the entire message is processed"),
nil, nil, nil},
{[]byte{0xFB, 0x1A, 0x18, 0x47, 0x39, 0x3C, 0x9F, 0x45, 0x5F, 0x29, 0x4C, 0x51, 0x42, 0x30, 0xA6, 0xB9},
nil, nil, nil},
// msg = \epsilon (empty string)
{[]byte{}, nil, nil, nil},
// msg == nil
{nil, nil, nil, nil},
}
// Run our inputs through commit and record the outputs
func init() {
for i := range testResults {
entry := &testResults[i]
entry.commit, entry.decommit, entry.err = Commit(entry.msg)
}
}
// Computing commitments should never produce errors
func TestCommitWithoutErrors(t *testing.T) {
for _, entry := range testResults {
if entry.err != nil {
t.Errorf("received Commit(%v): %v", entry.msg, entry.err)
}
}
}
// Commitments should be 256b == 64B in length
func TestCommitmentsAreExpectedLength(t *testing.T) {
const expLen = 256 / 8
for _, entry := range testResults {
if len(entry.commit) != expLen {
t.Errorf("commitment is not expected length: %v != %v", len(entry.commit), expLen)
}
}
}
// Decommit cannot be nil
func TestCommmitProducesDecommit(t *testing.T) {
for _, entry := range testResults {
if entry.decommit == nil {
t.Errorf("decommit cannot be nil: Commit(%v)", entry.msg)
}
}
}
// Decommit value should contain the same message
func TestCommmitProducesDecommitWithSameMessage(t *testing.T) {
for _, entry := range testResults {
if !bytes.Equal(entry.msg, entry.decommit.Msg) {
t.Errorf("decommit.msg != msg: %v != %v", entry.msg, entry.decommit.Msg)
}
}
}
// Commitments should be unique
func TestCommmitProducesDistinctCommitments(t *testing.T) {
seen := make(map[[Size]byte]bool)
// Check the pre-computed commitments for uniquness
for _, entry := range testResults {
// Slices cannot be used as hash keys, so we need to copy into
// an array. Oh, go-lang.
var cee [Size]byte
copy(cee[:], entry.commit)
// Ensure each commit is unique
if seen[cee] {
t.Errorf("duplicate commit found: %v", cee)
}
seen[cee] = true
}
}
// Commitments should be unique even for the same message since the nonce is
// randomly selected
func TestCommmitDistinctCommitments(t *testing.T) {
seen := make(map[[Size]byte]bool)
msg := []byte("black lives matter")
const iterations = 1000
// Check the pre-computed commitments for uniquness
for i := 0; i < iterations; i++ {
// Compute a commitment
c, _, err := Commit(msg)
if err != nil {
t.Error(err)
}
// Slices cannot be used as hash keys, so copy into an array
var cee [Size]byte
copy(cee[:], []byte(c))
// Ensure each commit is unique
if seen[cee] {
t.Errorf("duplicate commit found: %v", cee)
}
seen[cee] = true
}
}
// Nonces must be 256b = 64B
func TestCommmitNonceIsExpectedLength(t *testing.T) {
const expLen = 256 / 8
// Check the pre-computed nonces
for _, entry := range testResults {
if len(entry.decommit.r) != expLen {
t.Errorf("nonce is not expected length: %v != %v", len(entry.decommit.r), expLen)
}
}
}
// Randomly selected nonces will be unique with overwhelming probability
func TestCommmitProducesDistinctNonces(t *testing.T) {
seen := make(map[[Size]byte]bool)
msg := []byte("black lives matter")
const iterations = 1000
// Check the pre-computed commitments for uniquness
for i := 0; i < iterations; i++ {
// Compute a commitment
_, dee, err := Commit(msg)
if err != nil {
t.Error(err)
}
// Ensure each nonce is unique
if seen[dee.r] {
t.Errorf("duplicate nonce found: %v", dee.r)
}
seen[dee.r] = true
}
}
func TestOpenOnValidCommitments(t *testing.T) {
for _, entry := range testResults {
// Open each commitment
ok, err := Open(entry.commit, *entry.decommit)
// There should be no error
if err != nil {
t.Error(err)
}
// The commitments should verify
if !ok {
t.Errorf("commitment failed to open: %v", entry.msg)
}
}
}
func TestOpenOnModifiedNonce(t *testing.T) {
for _, entry := range testResults {
dʹ := copyWitness(entry.decommit)
// Modify the nonce
dʹ.r[0] ^= 0x40
// Open and check for failure
ok, err := Open(entry.commit, *dʹ)
assertFailedOpen(t, ok, err)
}
}
func TestOpenOnZeroPrefixNonce(t *testing.T) {
for _, entry := range testResults {
dʹ := copyWitness(entry.decommit)
// Modify the nonce
dʹ.r[0] = 0x00
dʹ.r[1] = 0x00
dʹ.r[2] = 0x00
dʹ.r[3] = 0x00
dʹ.r[4] = 0x00
dʹ.r[5] = 0x00
dʹ.r[6] = 0x00
dʹ.r[7] = 0x00
dʹ.r[8] = 0x00
dʹ.r[9] = 0x00
dʹ.r[10] = 0x00
// Open and check for failure
ok, err := Open(entry.commit, *dʹ)
assertFailedOpen(t, ok, err)
}
}
// Makes a deep copy of a Witness
func copyWitness(d *Witness) *Witness {
msg := make([]byte, len(d.Msg))
var r [Size]byte
copy(msg, d.Msg)
copy(r[:], d.r[:])
return &Witness{msg, r}
}
// Asserts that err != nil, and ok == false.
func assertFailedOpen(t *testing.T, ok bool, err error) {
// There should be no error
if err != nil {
t.Error(err)
}
// But the commitments should fail
if ok {
t.Error("commitment was verified but was expected to fail")
}
}
// An unrelated message should fail on open
func TestOpenOnNewMessage(t *testing.T) {
for _, entry := range testResults {
dʹ := copyWitness(entry.decommit)
// Use a distinct message
dʹ.Msg = []byte("no one expects the spanish inquisition")
// Open and check for failure
ok, err := Open(entry.commit, *dʹ)
assertFailedOpen(t, ok, err)
}
}
// An appended message should fail on open
func TestOpenOnAppendedMessage(t *testing.T) {
for _, entry := range testResults {
dʹ := copyWitness(entry.decommit)
// Modify the message
dʹ.Msg = []byte("no one expects the spanish inquisition")
// Open and check for failure
ok, err := Open(entry.commit, *dʹ)
assertFailedOpen(t, ok, err)
}
}
// A modified message should fail on open
func TestOpenOnModifiedMessage(t *testing.T) {
for _, entry := range testResults {
// Skip the empty string message for this test case
if len(entry.msg) == 0 {
continue
}
// Modify the message _in situ_
dʹ := copyWitness(entry.decommit)
dʹ.Msg[1] ^= 0x99
// Open and check for failure
ok, err := Open(entry.commit, *dʹ)
assertFailedOpen(t, ok, err)
}
}
// A modified commitment should fail on open
func TestOpenOnModifiedCommitment(t *testing.T) {
for _, entry := range testResults {
// Copy and then modify the commitment
cʹ := make([]byte, Size)
copy(cʹ[:], entry.commit)
cʹ[6] ^= 0x33
// Open and check for failure
ok, err := Open(cʹ, *entry.decommit)
assertFailedOpen(t, ok, err)
}
}
// An empty decommit should fail to open
func TestOpenOnDefaultDecommitObject(t *testing.T) {
for _, entry := range testResults {
// Open and check for failure
ok, err := Open(entry.commit, Witness{})
assertFailedOpen(t, ok, err)
}
}
// A nil commit should return an error
func TestOpenOnNilCommitment(t *testing.T) {
_, err := Open(nil, Witness{})
assertError(t, err)
}
// Verifies that err != nil
func assertError(t *testing.T, err error) {
if err == nil {
t.Error("expected an error but received nil")
}
}
// Ill-formed commitment should produce an error
func TestOpenOnLongCommitment(t *testing.T) {
tooLong := make([]byte, Size+1)
_, err := Open(tooLong, Witness{})
assertError(t, err)
}
// Ill-formed commitment should produce an error
func TestOpenOnShortCommitment(t *testing.T) {
tooShort := make([]byte, Size-1)
_, err := Open(tooShort, Witness{})
assertError(t, err)
}
// Tests that marshal-unmarshal is the identity function
func TestWitnessMarshalRoundTrip(t *testing.T) {
expected := &Witness{
[]byte("I'm the dude. So that's what you call me"),
[Size]byte{0xAC},
}
// Marhal and test
jsonBytes, err := json.Marshal(expected)
require.NoError(t, err)
require.NotNil(t, jsonBytes)
// Unmarshal and test
actual := &Witness{}
require.NoError(t, json.Unmarshal(jsonBytes, actual))
require.Equal(t, expected.Msg, actual.Msg)
require.Equal(t, expected.r, actual.r)
}
// Tests that marshal-unmarshal is the identity function
func TestCommitmentMarshalRoundTrip(t *testing.T) {
expected := Commitment([]byte("That or uh his-dudeness or duder or el duderino."))
// Marhal and test
jsonBytes, err := json.Marshal(expected)
require.NoError(t, err)
require.NotNil(t, jsonBytes)
// Unmarshal and test
actual := Commitment{}
require.NoError(t, json.Unmarshal(jsonBytes, &actual))
require.Equal(t, []byte(expected), []byte(actual))
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+863
View File
@@ -0,0 +1,863 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package curves
import (
"crypto/elliptic"
"encoding/hex"
"encoding/json"
"fmt"
"hash"
"io"
"math/big"
"sync"
"github.com/onsonr/sonr/pkg/crypto/core/curves/native/bls12381"
)
var (
k256Initonce sync.Once
k256 Curve
bls12381g1Initonce sync.Once
bls12381g1 Curve
bls12381g2Initonce sync.Once
bls12381g2 Curve
bls12377g1Initonce sync.Once
bls12377g1 Curve
bls12377g2Initonce sync.Once
bls12377g2 Curve
p256Initonce sync.Once
p256 Curve
ed25519Initonce sync.Once
ed25519 Curve
pallasInitonce sync.Once
pallas Curve
)
const (
K256Name = "secp256k1"
BLS12381G1Name = "BLS12381G1"
BLS12381G2Name = "BLS12381G2"
BLS12831Name = "BLS12831"
P256Name = "P-256"
ED25519Name = "ed25519"
PallasName = "pallas"
BLS12377G1Name = "BLS12377G1"
BLS12377G2Name = "BLS12377G2"
BLS12377Name = "BLS12377"
)
const scalarBytes = 32
// Scalar represents an element of the scalar field \mathbb{F}_q
// of the elliptic curve construction.
type Scalar interface {
// Random returns a random scalar using the provided reader
// to retrieve bytes
Random(reader io.Reader) Scalar
// Hash the specific bytes in a manner to yield a
// uniformly distributed scalar
Hash(bytes []byte) Scalar
// Zero returns the additive identity element
Zero() Scalar
// One returns the multiplicative identity element
One() Scalar
// IsZero returns true if this element is the additive identity element
IsZero() bool
// IsOne returns true if this element is the multiplicative identity element
IsOne() bool
// IsOdd returns true if this element is odd
IsOdd() bool
// IsEven returns true if this element is even
IsEven() bool
// New returns an element with the value equal to `value`
New(value int) Scalar
// Cmp returns
// -2 if this element is in a different field than rhs
// -1 if this element is less than rhs
// 0 if this element is equal to rhs
// 1 if this element is greater than rhs
Cmp(rhs Scalar) int
// Square returns element*element
Square() Scalar
// Double returns element+element
Double() Scalar
// Invert returns element^-1 mod p
Invert() (Scalar, error)
// Sqrt computes the square root of this element if it exists.
Sqrt() (Scalar, error)
// Cube returns element*element*element
Cube() Scalar
// Add returns element+rhs
Add(rhs Scalar) Scalar
// Sub returns element-rhs
Sub(rhs Scalar) Scalar
// Mul returns element*rhs
Mul(rhs Scalar) Scalar
// MulAdd returns element * y + z mod p
MulAdd(y, z Scalar) Scalar
// Div returns element*rhs^-1 mod p
Div(rhs Scalar) Scalar
// Neg returns -element mod p
Neg() Scalar
// SetBigInt returns this element set to the value of v
SetBigInt(v *big.Int) (Scalar, error)
// BigInt returns this element as a big integer
BigInt() *big.Int
// Point returns the associated point for this scalar
Point() Point
// Bytes returns the canonical byte representation of this scalar
Bytes() []byte
// SetBytes creates a scalar from the canonical representation expecting the exact number of bytes needed to represent the scalar
SetBytes(bytes []byte) (Scalar, error)
// SetBytesWide creates a scalar expecting double the exact number of bytes needed to represent the scalar which is reduced by the modulus
SetBytesWide(bytes []byte) (Scalar, error)
// Clone returns a cloned Scalar of this value
Clone() Scalar
}
type PairingScalar interface {
Scalar
SetPoint(p Point) PairingScalar
}
func unmarshalScalar(input []byte) (*Curve, []byte, error) {
sep := byte(':')
i := 0
for ; i < len(input); i++ {
if input[i] == sep {
break
}
}
name := string(input[:i])
curve := GetCurveByName(name)
if curve == nil {
return nil, nil, fmt.Errorf("unrecognized curve")
}
return curve, input[i+1:], nil
}
func scalarMarshalBinary(scalar Scalar) ([]byte, error) {
// All scalars are 32 bytes long
// The last 32 bytes are the actual value
// The first remaining bytes are the curve name
// separated by a colon
name := []byte(scalar.Point().CurveName())
output := make([]byte, len(name)+1+scalarBytes)
copy(output[:len(name)], name)
output[len(name)] = byte(':')
copy(output[len(name)+1:], scalar.Bytes())
return output, nil
}
func scalarUnmarshalBinary(input []byte) (Scalar, error) {
// All scalars are 32 bytes long
// The first 32 bytes are the actual value
// The remaining bytes are the curve name
if len(input) < scalarBytes+1+len(P256Name) {
return nil, fmt.Errorf("invalid byte sequence")
}
sc, data, err := unmarshalScalar(input)
if err != nil {
return nil, err
}
return sc.Scalar.SetBytes(data)
}
func scalarMarshalText(scalar Scalar) ([]byte, error) {
// All scalars are 32 bytes long
// For text encoding we put the curve name first for readability
// separated by a colon, then the hex encoding of the scalar
// which avoids the base64 weakness with strict mode or not
name := []byte(scalar.Point().CurveName())
output := make([]byte, len(name)+1+scalarBytes*2)
copy(output[:len(name)], name)
output[len(name)] = byte(':')
_ = hex.Encode(output[len(name)+1:], scalar.Bytes())
return output, nil
}
func scalarUnmarshalText(input []byte) (Scalar, error) {
if len(input) < scalarBytes*2+len(P256Name)+1 {
return nil, fmt.Errorf("invalid byte sequence")
}
curve, data, err := unmarshalScalar(input)
if err != nil {
return nil, err
}
var t [scalarBytes]byte
_, err = hex.Decode(t[:], data)
if err != nil {
return nil, err
}
return curve.Scalar.SetBytes(t[:])
}
func scalarMarshalJson(scalar Scalar) ([]byte, error) {
m := make(map[string]string, 2)
m["type"] = scalar.Point().CurveName()
m["value"] = hex.EncodeToString(scalar.Bytes())
return json.Marshal(m)
}
func scalarUnmarshalJson(input []byte) (Scalar, error) {
var m map[string]string
err := json.Unmarshal(input, &m)
if err != nil {
return nil, err
}
curve := GetCurveByName(m["type"])
if curve == nil {
return nil, fmt.Errorf("invalid type")
}
s, err := hex.DecodeString(m["value"])
if err != nil {
return nil, err
}
S, err := curve.Scalar.SetBytes(s)
if err != nil {
return nil, err
}
return S, nil
}
// Point represents an elliptic curve point
type Point interface {
Random(reader io.Reader) Point
Hash(bytes []byte) Point
Identity() Point
Generator() Point
IsIdentity() bool
IsNegative() bool
IsOnCurve() bool
Double() Point
Scalar() Scalar
Neg() Point
Add(rhs Point) Point
Sub(rhs Point) Point
Mul(rhs Scalar) Point
Equal(rhs Point) bool
Set(x, y *big.Int) (Point, error)
ToAffineCompressed() []byte
ToAffineUncompressed() []byte
FromAffineCompressed(bytes []byte) (Point, error)
FromAffineUncompressed(bytes []byte) (Point, error)
CurveName() string
SumOfProducts(points []Point, scalars []Scalar) Point
}
type PairingPoint interface {
Point
OtherGroup() PairingPoint
Pairing(rhs PairingPoint) Scalar
MultiPairing(...PairingPoint) Scalar
}
func pointMarshalBinary(point Point) ([]byte, error) {
// Always stores points in compressed form
// The first bytes are the curve name
// separated by a colon followed by the compressed point
// bytes
t := point.ToAffineCompressed()
name := []byte(point.CurveName())
output := make([]byte, len(name)+1+len(t))
copy(output[:len(name)], name)
output[len(name)] = byte(':')
copy(output[len(output)-len(t):], t)
return output, nil
}
func pointUnmarshalBinary(input []byte) (Point, error) {
if len(input) < scalarBytes+1+len(P256Name) {
return nil, fmt.Errorf("invalid byte sequence")
}
sep := byte(':')
i := 0
for ; i < len(input); i++ {
if input[i] == sep {
break
}
}
name := string(input[:i])
curve := GetCurveByName(name)
if curve == nil {
return nil, fmt.Errorf("unrecognized curve")
}
return curve.Point.FromAffineCompressed(input[i+1:])
}
func pointMarshalText(point Point) ([]byte, error) {
// Always stores points in compressed form
// The first bytes are the curve name
// separated by a colon followed by the compressed point
// bytes
t := point.ToAffineCompressed()
name := []byte(point.CurveName())
output := make([]byte, len(name)+1+len(t)*2)
copy(output[:len(name)], name)
output[len(name)] = byte(':')
hex.Encode(output[len(output)-len(t)*2:], t)
return output, nil
}
func pointUnmarshalText(input []byte) (Point, error) {
if len(input) < scalarBytes*2+1+len(P256Name) {
return nil, fmt.Errorf("invalid byte sequence")
}
sep := byte(':')
i := 0
for ; i < len(input); i++ {
if input[i] == sep {
break
}
}
name := string(input[:i])
curve := GetCurveByName(name)
if curve == nil {
return nil, fmt.Errorf("unrecognized curve")
}
buffer := make([]byte, (len(input)-i)/2)
_, err := hex.Decode(buffer, input[i+1:])
if err != nil {
return nil, err
}
return curve.Point.FromAffineCompressed(buffer)
}
func pointMarshalJson(point Point) ([]byte, error) {
m := make(map[string]string, 2)
m["type"] = point.CurveName()
m["value"] = hex.EncodeToString(point.ToAffineCompressed())
return json.Marshal(m)
}
func pointUnmarshalJson(input []byte) (Point, error) {
var m map[string]string
err := json.Unmarshal(input, &m)
if err != nil {
return nil, err
}
curve := GetCurveByName(m["type"])
if curve == nil {
return nil, fmt.Errorf("invalid type")
}
p, err := hex.DecodeString(m["value"])
if err != nil {
return nil, err
}
P, err := curve.Point.FromAffineCompressed(p)
if err != nil {
return nil, err
}
return P, nil
}
// Curve represents a named elliptic curve with a scalar field and point group
type Curve struct {
Scalar Scalar
Point Point
Name string
}
func (c Curve) ScalarBaseMult(sc Scalar) Point {
return c.Point.Generator().Mul(sc)
}
func (c Curve) NewGeneratorPoint() Point {
return c.Point.Generator()
}
func (c Curve) NewIdentityPoint() Point {
return c.Point.Identity()
}
func (c Curve) NewScalar() Scalar {
return c.Scalar.Zero()
}
// ToEllipticCurve returns the equivalent of this curve as the go interface `elliptic.Curve`
func (c Curve) ToEllipticCurve() (elliptic.Curve, error) {
err := fmt.Errorf("can't convert %s", c.Name)
switch c.Name {
case K256Name:
return K256Curve(), nil
case BLS12381G1Name:
return nil, err
case BLS12381G2Name:
return nil, err
case BLS12831Name:
return nil, err
case P256Name:
return NistP256Curve(), nil
case ED25519Name:
return nil, err
case PallasName:
return nil, err
case BLS12377G1Name:
return nil, err
case BLS12377G2Name:
return nil, err
case BLS12377Name:
return nil, err
default:
return nil, err
}
}
// PairingCurve represents a named elliptic curve
// that supports pairings
type PairingCurve struct {
Scalar PairingScalar
PointG1 PairingPoint
PointG2 PairingPoint
GT Scalar
Name string
}
func (c PairingCurve) ScalarG1BaseMult(sc Scalar) PairingPoint {
return c.PointG1.Generator().Mul(sc).(PairingPoint)
}
func (c PairingCurve) ScalarG2BaseMult(sc Scalar) PairingPoint {
return c.PointG2.Generator().Mul(sc).(PairingPoint)
}
func (c PairingCurve) NewG1GeneratorPoint() PairingPoint {
return c.PointG1.Generator().(PairingPoint)
}
func (c PairingCurve) NewG2GeneratorPoint() PairingPoint {
return c.PointG2.Generator().(PairingPoint)
}
func (c PairingCurve) NewG1IdentityPoint() PairingPoint {
return c.PointG1.Identity().(PairingPoint)
}
func (c PairingCurve) NewG2IdentityPoint() PairingPoint {
return c.PointG2.Identity().(PairingPoint)
}
func (c PairingCurve) NewScalar() PairingScalar {
return c.Scalar.Zero().(PairingScalar)
}
// GetCurveByName returns the correct `Curve` given the name
func GetCurveByName(name string) *Curve {
switch name {
case K256Name:
return K256()
case BLS12381G1Name:
return BLS12381G1()
case BLS12381G2Name:
return BLS12381G2()
case BLS12831Name:
return BLS12381G1()
case P256Name:
return P256()
case ED25519Name:
return ED25519()
case PallasName:
return PALLAS()
case BLS12377G1Name:
return BLS12377G1()
case BLS12377G2Name:
return BLS12377G2()
case BLS12377Name:
return BLS12377G1()
default:
return nil
}
}
func GetPairingCurveByName(name string) *PairingCurve {
switch name {
case BLS12381G1Name:
return BLS12381(BLS12381G1().NewIdentityPoint())
case BLS12381G2Name:
return BLS12381(BLS12381G2().NewIdentityPoint())
case BLS12831Name:
return BLS12381(BLS12381G1().NewIdentityPoint())
default:
return nil
}
}
// BLS12381G1 returns the BLS12-381 curve with points in G1
func BLS12381G1() *Curve {
bls12381g1Initonce.Do(bls12381g1Init)
return &bls12381g1
}
func bls12381g1Init() {
bls12381g1 = Curve{
Scalar: &ScalarBls12381{
Value: bls12381.Bls12381FqNew(),
point: new(PointBls12381G1),
},
Point: new(PointBls12381G1).Identity(),
Name: BLS12381G1Name,
}
}
// BLS12381G2 returns the BLS12-381 curve with points in G2
func BLS12381G2() *Curve {
bls12381g2Initonce.Do(bls12381g2Init)
return &bls12381g2
}
func bls12381g2Init() {
bls12381g2 = Curve{
Scalar: &ScalarBls12381{
Value: bls12381.Bls12381FqNew(),
point: new(PointBls12381G2),
},
Point: new(PointBls12381G2).Identity(),
Name: BLS12381G2Name,
}
}
func BLS12381(preferredPoint Point) *PairingCurve {
return &PairingCurve{
Scalar: &ScalarBls12381{
Value: bls12381.Bls12381FqNew(),
point: preferredPoint,
},
PointG1: &PointBls12381G1{
Value: new(bls12381.G1).Identity(),
},
PointG2: &PointBls12381G2{
Value: new(bls12381.G2).Identity(),
},
GT: &ScalarBls12381Gt{
Value: new(bls12381.Gt).SetOne(),
},
Name: BLS12831Name,
}
}
// BLS12377G1 returns the BLS12-377 curve with points in G1
func BLS12377G1() *Curve {
bls12377g1Initonce.Do(bls12377g1Init)
return &bls12377g1
}
func bls12377g1Init() {
bls12377g1 = Curve{
Scalar: &ScalarBls12377{
value: new(big.Int),
point: new(PointBls12377G1),
},
Point: new(PointBls12377G1).Identity(),
Name: BLS12377G1Name,
}
}
// BLS12377G2 returns the BLS12-377 curve with points in G2
func BLS12377G2() *Curve {
bls12377g2Initonce.Do(bls12377g2Init)
return &bls12377g2
}
func bls12377g2Init() {
bls12377g2 = Curve{
Scalar: &ScalarBls12377{
value: new(big.Int),
point: new(PointBls12377G2),
},
Point: new(PointBls12377G2).Identity(),
Name: BLS12377G2Name,
}
}
// K256 returns the secp256k1 curve
func K256() *Curve {
k256Initonce.Do(k256Init)
return &k256
}
func k256Init() {
k256 = Curve{
Scalar: new(ScalarK256).Zero(),
Point: new(PointK256).Identity(),
Name: K256Name,
}
}
func P256() *Curve {
p256Initonce.Do(p256Init)
return &p256
}
func p256Init() {
p256 = Curve{
Scalar: new(ScalarP256).Zero(),
Point: new(PointP256).Identity(),
Name: P256Name,
}
}
func ED25519() *Curve {
ed25519Initonce.Do(ed25519Init)
return &ed25519
}
func ed25519Init() {
ed25519 = Curve{
Scalar: new(ScalarEd25519).Zero(),
Point: new(PointEd25519).Identity(),
Name: ED25519Name,
}
}
func PALLAS() *Curve {
pallasInitonce.Do(pallasInit)
return &pallas
}
func pallasInit() {
pallas = Curve{
Scalar: new(ScalarPallas).Zero(),
Point: new(PointPallas).Identity(),
Name: PallasName,
}
}
// https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-11#appendix-G.2.1
func osswu3mod4(u *big.Int, p *sswuParams) (x, y *big.Int) {
params := p.Params
field := NewField(p.Params.P)
tv1 := field.NewElement(u)
tv1 = tv1.Mul(tv1) // tv1 = u^2
tv3 := field.NewElement(p.Z).Mul(tv1) // tv3 = Z * tv1
tv2 := tv3.Mul(tv3) // tv2 = tv3^2
xd := tv2.Add(tv3) // xd = tv2 + tv3
x1n := xd.Add(field.One()) // x1n = (xd + 1)
x1n = x1n.Mul(field.NewElement(p.B)) // x1n * B
aNeg := field.NewElement(p.A).Neg()
xd = xd.Mul(aNeg) // xd = -A * xd
if xd.Value.Cmp(big.NewInt(0)) == 0 {
xd = field.NewElement(p.Z).Mul(field.NewElement(p.A)) // xd = Z * A
}
tv2 = xd.Mul(xd) // tv2 = xd^2
gxd := tv2.Mul(xd) // gxd = tv2 * xd
tv2 = tv2.Mul(field.NewElement(p.A)) // tv2 = A * tv2
gx1 := x1n.Mul(x1n) // gx1 = x1n^2
gx1 = gx1.Add(tv2) // gx1 = gx1 + tv2
gx1 = gx1.Mul(x1n) // gx1 = gx1 * x1n
tv2 = gxd.Mul(field.NewElement(p.B)) // tv2 = B * gxd
gx1 = gx1.Add(tv2) // gx1 = gx1 + tv2
tv4 := gxd.Mul(gxd) // tv4 = gxd^2
tv2 = gx1.Mul(gxd) // tv2 = gx1 * gxd
tv4 = tv4.Mul(tv2) // tv4 = tv4 * tv2
y1 := tv4.Pow(field.NewElement(p.C1))
y1 = y1.Mul(tv2) // y1 = y1 * tv2
x2n := tv3.Mul(x1n) // x2n = tv3 * x1n
y2 := y1.Mul(field.NewElement(p.C2)) // y2 = y1 * c2
y2 = y2.Mul(tv1) // y2 = y2 * tv1
y2 = y2.Mul(field.NewElement(u)) // y2 = y2 * u
tv2 = y1.Mul(y1) // tv2 = y1^2
tv2 = tv2.Mul(gxd) // tv2 = tv2 * gxd
e2 := tv2.Value.Cmp(gx1.Value) == 0
// If e2, x = x1, else x = x2
if e2 {
x = x1n.Value
} else {
x = x2n.Value
}
// xn / xd
x.Mul(x, new(big.Int).ModInverse(xd.Value, params.P))
x.Mod(x, params.P)
// If e2, y = y1, else y = y2
if e2 {
y = y1.Value
} else {
y = y2.Value
}
uBytes := u.Bytes()
yBytes := y.Bytes()
usign := uBytes[len(uBytes)-1] & 1
ysign := yBytes[len(yBytes)-1] & 1
// Fix sign of y
if usign != ysign {
y.Neg(y)
y.Mod(y, params.P)
}
return
}
func expandMsgXmd(h hash.Hash, msg, domain []byte, outLen int) ([]byte, error) {
domainLen := uint8(len(domain))
if domainLen > 255 {
return nil, fmt.Errorf("invalid domain length")
}
// DST_prime = DST || I2OSP(len(DST), 1)
// b_0 = H(Z_pad || msg || l_i_b_str || I2OSP(0, 1) || DST_prime)
_, _ = h.Write(make([]byte, h.BlockSize()))
_, _ = h.Write(msg)
_, _ = h.Write([]byte{uint8(outLen >> 8), uint8(outLen)})
_, _ = h.Write([]byte{0})
_, _ = h.Write(domain)
_, _ = h.Write([]byte{domainLen})
b0 := h.Sum(nil)
// b_1 = H(b_0 || I2OSP(1, 1) || DST_prime)
h.Reset()
_, _ = h.Write(b0)
_, _ = h.Write([]byte{1})
_, _ = h.Write(domain)
_, _ = h.Write([]byte{domainLen})
b1 := h.Sum(nil)
// b_i = H(strxor(b_0, b_(i - 1)) || I2OSP(i, 1) || DST_prime)
ell := (outLen + h.Size() - 1) / h.Size()
bi := b1
out := make([]byte, outLen)
for i := 1; i < ell; i++ {
h.Reset()
// b_i = H(strxor(b_0, b_(i - 1)) || I2OSP(i, 1) || DST_prime)
tmp := make([]byte, h.Size())
for j := 0; j < h.Size(); j++ {
tmp[j] = b0[j] ^ bi[j]
}
_, _ = h.Write(tmp)
_, _ = h.Write([]byte{1 + uint8(i)})
_, _ = h.Write(domain)
_, _ = h.Write([]byte{domainLen})
// b_1 || ... || b_(ell - 1)
copy(out[(i-1)*h.Size():i*h.Size()], bi[:])
bi = h.Sum(nil)
}
// b_ell
copy(out[(ell-1)*h.Size():], bi[:])
return out[:outLen], nil
}
func bhex(s string) *big.Int {
r, _ := new(big.Int).SetString(s, 16)
return r
}
type sswuParams struct {
Params *elliptic.CurveParams
C1, C2, A, B, Z *big.Int
}
// sumOfProductsPippenger implements a version of Pippenger's algorithm.
//
// The algorithm works as follows:
//
// Let `n` be a number of point-scalar pairs.
// Let `w` be a window of bits (6..8, chosen based on `n`, see cost factor).
//
// 1. Prepare `2^(w-1) - 1` buckets with indices `[1..2^(w-1))` initialized with identity points.
// Bucket 0 is not needed as it would contain points multiplied by 0.
// 2. Convert scalars to a radix-`2^w` representation with signed digits in `[-2^w/2, 2^w/2]`.
// Note: only the last digit may equal `2^w/2`.
// 3. Starting with the last window, for each point `i=[0..n)` add it to a a bucket indexed by
// the point's scalar's value in the window.
// 4. Once all points in a window are sorted into buckets, add buckets by multiplying each
// by their index. Efficient way of doing it is to start with the last bucket and compute two sums:
// intermediate sum from the last to the first, and the full sum made of all intermediate sums.
// 5. Shift the resulting sum of buckets by `w` bits by using `w` doublings.
// 6. Add to the return value.
// 7. Repeat the loop.
//
// Approximate cost w/o wNAF optimizations (A = addition, D = doubling):
//
// ```ascii
// cost = (n*A + 2*(2^w/2)*A + w*D + A)*256/w
//
// | | | | |
// | | | | looping over 256/w windows
// | | | adding to the result
// sorting points | shifting the sum by w bits (to the next window, starting from last window)
// one by one |
// into buckets adding/subtracting all buckets
// multiplied by their indexes
// using a sum of intermediate sums
//
// ```
//
// For large `n`, dominant factor is (n*256/w) additions.
// However, if `w` is too big and `n` is not too big, then `(2^w/2)*A` could dominate.
// Therefore, the optimal choice of `w` grows slowly as `n` grows.
//
// # For constant time we use a fixed window of 6
//
// This algorithm is adapted from section 4 of <https://eprint.iacr.org/2012/549.pdf>.
// and https://cacr.uwaterloo.ca/techreports/2010/cacr2010-26.pdf
func sumOfProductsPippenger(points []Point, scalars []*big.Int) Point {
if len(points) != len(scalars) {
return nil
}
const w = 6
bucketSize := (1 << w) - 1
windows := make([]Point, 255/w+1)
for i := range windows {
windows[i] = points[0].Identity()
}
bucket := make([]Point, bucketSize)
for j := 0; j < len(windows); j++ {
for i := 0; i < bucketSize; i++ {
bucket[i] = points[0].Identity()
}
for i := 0; i < len(scalars); i++ {
index := bucketSize & int(new(big.Int).Rsh(scalars[i], uint(w*j)).Int64())
if index != 0 {
bucket[index-1] = bucket[index-1].Add(points[i])
}
}
acc, sum := windows[j].Identity(), windows[j].Identity()
for i := bucketSize - 1; i >= 0; i-- {
sum = sum.Add(bucket[i])
acc = acc.Add(sum)
}
windows[j] = acc
}
acc := windows[0].Identity()
for i := len(windows) - 1; i >= 0; i-- {
for j := 0; j < w; j++ {
acc = acc.Double()
}
acc = acc.Add(windows[i])
}
return acc
}

Some files were not shown because too many files have changed in this diff Show More