Procházet zdrojové kódy

Merge branch 'master' into staging-client

Rod Hynes před 2 týdny
rodič
revize
affa0dfab7
38 změnil soubory, kde provedl 574 přidání a 321 odebrání
  1. 1 1
      go.mod
  2. 2 0
      go.sum
  3. 2 0
      psiphon/common/inproxy/api.go
  4. 1 1
      psiphon/common/inproxy/broker.go
  5. 2 0
      psiphon/common/inproxy/inproxy_disabled.go
  6. 47 7
      psiphon/common/inproxy/matcher.go
  7. 5 0
      psiphon/common/inproxy/proxy.go
  8. 38 5
      psiphon/common/inproxy/webrtc.go
  9. 24 0
      psiphon/config.go
  10. 1 0
      psiphon/controller.go
  11. 6 4
      psiphon/dataStoreRecovery_test.go
  12. 1 1
      psiphon/dataStore_bolt.go
  13. 9 0
      psiphon/inproxy.go
  14. 1 1
      psiphon/notice.go
  15. 43 3
      psiphon/server/pb/psiphond/inproxy_broker.pb.go
  16. 23 3
      psiphon/server/pb/psiphond/inproxy_dial_params.pb.go
  17. binární
      psiphon/server/pb/psiphond/psiphond.desc
  18. binární
      psiphon/server/pb/router/router.desc
  19. 4 0
      psiphon/server/proto/ca.psiphon.psiphond/inproxy_broker.proto
  20. 2 1
      psiphon/server/proto/ca.psiphon.psiphond/inproxy_dial_params.proto
  21. 1 1
      psiphon/server/proto/compile_protobufs.sh
  22. 0 1
      psiphon/server/protobufConverter.go
  23. 2 0
      psiphon/server/server_test.go
  24. 4 2
      vendor/filippo.io/edwards25519/README.md
  25. 3 3
      vendor/filippo.io/edwards25519/doc.go
  26. 60 8
      vendor/filippo.io/edwards25519/extra.go
  27. 17 17
      vendor/filippo.io/edwards25519/field/fe.go
  28. 1 2
      vendor/filippo.io/edwards25519/field/fe_amd64.go
  29. 111 92
      vendor/filippo.io/edwards25519/field/fe_amd64.s
  30. 1 2
      vendor/filippo.io/edwards25519/field/fe_amd64_noasm.go
  31. 0 16
      vendor/filippo.io/edwards25519/field/fe_arm64.go
  32. 0 42
      vendor/filippo.io/edwards25519/field/fe_arm64.s
  33. 0 12
      vendor/filippo.io/edwards25519/field/fe_arm64_noasm.go
  34. 88 82
      vendor/filippo.io/edwards25519/field/fe_generic.go
  35. 53 0
      vendor/filippo.io/edwards25519/pull.sh
  36. 18 9
      vendor/filippo.io/edwards25519/scalar.go
  37. 1 3
      vendor/filippo.io/edwards25519/tables.go
  38. 2 2
      vendor/modules.txt

+ 1 - 1
go.mod

@@ -32,7 +32,7 @@ replace github.com/pion/ice/v2 => ./replace/ice
 replace github.com/pion/webrtc/v3 => ./replace/webrtc
 
 require (
-	filippo.io/edwards25519 v1.1.0
+	filippo.io/edwards25519 v1.2.0
 	github.com/Jigsaw-Code/outline-sdk v0.0.16
 	github.com/Jigsaw-Code/outline-ss-server v1.8.0
 	github.com/Psiphon-Inc/rotate-safe-writer v0.0.0-20210303140923-464a7a37606e

+ 2 - 0
go.sum

@@ -2,6 +2,8 @@ filippo.io/bigmod v0.0.1 h1:OaEqDr3gEbofpnHbGqZweSL/bLMhy1pb54puiCDeuOA=
 filippo.io/bigmod v0.0.1/go.mod h1:KyzqAbH7bRH6MOuOF1TPfUjvLoi0mRF2bIyD2ouRNQI=
 filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
 filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
+filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
+filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
 filippo.io/keygen v0.0.0-20230306160926-5201437acf8e h1:+xwUCyMiCWKWsI0RowhzB4sngpUdMHgU6lLuWJCX5Dg=
 filippo.io/keygen v0.0.0-20230306160926-5201437acf8e/go.mod h1:ZGSiF/b2hd6MRghF/cid0vXw8pXykRTmIu+JSPw/NCQ=
 github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=

+ 2 - 0
psiphon/common/inproxy/api.go

@@ -865,6 +865,7 @@ func (request *ClientOfferRequest) ValidateAndGetLogFields(
 	logFields["has_common_compartment_ids"] = hasCommonCompartmentIDs
 	logFields["has_personal_compartment_ids"] = hasPersonalCompartmentIDs
 	logFields["ice_candidate_types"] = request.ICECandidateTypes
+	logFields["has_IPv4"] = sdpMetrics.hasIPv4
 	logFields["has_IPv6"] = sdpMetrics.hasIPv6
 	logFields["has_private_IP"] = sdpMetrics.hasPrivateIP
 	logFields["filtered_ice_candidates"] = sdpMetrics.filteredICECandidates
@@ -960,6 +961,7 @@ func (request *ProxyAnswerRequest) ValidateAndGetLogFields(
 	logFields["ice_candidate_types"] = request.ICECandidateTypes
 	logFields["answer_error"] = request.AnswerError
 	if sdpMetrics != nil {
+		logFields["has_IPv4"] = sdpMetrics.hasIPv4
 		logFields["has_IPv6"] = sdpMetrics.hasIPv6
 		logFields["has_private_IP"] = sdpMetrics.hasPrivateIP
 		logFields["filtered_ice_candidates"] = sdpMetrics.filteredICECandidates

+ 1 - 1
psiphon/common/inproxy/broker.go

@@ -1053,7 +1053,7 @@ func (b *Broker) handleClientOffer(
 			logFields["preferred_nat_match"] =
 				clientMatchOffer.Properties.IsPreferredNATMatch(&proxyMatchAnnouncement.Properties)
 
-			// TODO: also log proxy ice_candidate_types and has_IPv6; for the
+			// TODO: also log proxy ice_candidate_types and has_IPv4/6; for the
 			// client, these values are added by ValidateAndGetLogFields.
 		}
 		if timedOut {

+ 2 - 0
psiphon/common/inproxy/inproxy_disabled.go

@@ -61,6 +61,7 @@ type webRTCConn struct {
 type webRTCConfig struct {
 	Logger                      common.Logger
 	EnableDebugLogging          bool
+	ExcludeInterfaceName        string
 	WebRTCDialCoordinator       WebRTCDialCoordinator
 	ClientRootObfuscationSecret ObfuscationSecret
 	DoDTLSRandomization         bool
@@ -126,6 +127,7 @@ func GetQUICMaxPacketSizeAdjustment() int {
 
 type webRTCSDPMetrics struct {
 	iceCandidateTypes     []ICECandidateType
+	hasIPv4               bool
 	hasIPv6               bool
 	hasPrivateIP          bool
 	filteredICECandidates []string

+ 47 - 7
psiphon/common/inproxy/matcher.go

@@ -228,6 +228,7 @@ type MatchMetrics struct {
 	AnnouncementMatchIndex int
 	AnnouncementQueueSize  int
 	PendingAnswersSize     int
+	MatchDuration          time.Duration
 }
 
 // GetMetrics converts MatchMetrics to loggable fields.
@@ -235,6 +236,11 @@ func (metrics *MatchMetrics) GetMetrics() common.LogFields {
 	if metrics == nil {
 		return nil
 	}
+
+	// match_duration, the time between matchAllOffers starting and a match
+	// being made, is reported in microseconds. The common millisecond
+	// duration resolution is expected to be too coarse.
+
 	return common.LogFields{
 		"offer_deadline":           int64(metrics.OfferDeadline / time.Millisecond),
 		"offer_match_index":        metrics.OfferMatchIndex,
@@ -242,6 +248,7 @@ func (metrics *MatchMetrics) GetMetrics() common.LogFields {
 		"announcement_match_index": metrics.AnnouncementMatchIndex,
 		"announcement_queue_size":  metrics.AnnouncementQueueSize,
 		"pending_answers_size":     metrics.PendingAnswersSize,
+		"match_duration":           int64(metrics.MatchDuration / time.Microsecond),
 	}
 }
 
@@ -672,6 +679,9 @@ func (m *Matcher) matchWorker(ctx context.Context) {
 // matchAllOffers iterates over the queues, making all possible matches.
 func (m *Matcher) matchAllOffers() {
 
+	// Include lock acquisition time in MatchDuration metric.
+	startTime := time.Now()
+
 	m.announcementQueueMutex.Lock()
 	defer m.announcementQueueMutex.Unlock()
 	m.offerQueueMutex.Lock()
@@ -732,6 +742,12 @@ func (m *Matcher) matchAllOffers() {
 		// The index metrics predate the announcement multi-queue; now, with
 		// the multi-queue, announcement_index is how many announce entries
 		// were inspected before matching.
+		//
+		// MatchDuration is intended to capture a sample of matchAllOffer
+		// processing time without resorting to new log events.
+		// Limitation: MatchDuration does not fully represent matchAllOffer
+		// elapsed time since it doesn't include passes with no matches or
+		// time spent failing to match after the last match in the pass.
 
 		matchMetrics := &MatchMetrics{
 			OfferDeadline:          untilOfferDeadline,
@@ -740,6 +756,7 @@ func (m *Matcher) matchAllOffers() {
 			AnnouncementMatchIndex: announcementMatchIndex,
 			AnnouncementQueueSize:  m.announcementQueue.getLen(),
 			PendingAnswersSize:     m.pendingAnswers.ItemCount(),
+			MatchDuration:          time.Since(startTime),
 		}
 
 		offerEntry.matchMetrics.Store(matchMetrics)
@@ -914,6 +931,12 @@ func (m *Matcher) matchOffer(offerEntry *offerEntry) (*announcementEntry, int) {
 			}
 		}
 
+		// Currently, there is no check or preference that the offer and
+		// announce have at least one of hasIPv4 or hasIPv6 in common, as
+		// clients are allowed to offer with no candidates and IPv6
+		// transition mechanisms such as 4in6/6in4/464XLAT/Teredo/etc. may be
+		// available.
+
 		// Check if this is a preferred NAT match. Ultimately, a match may be
 		// made with potentially incompatible NATs, but the client/proxy
 		// reported NAT types may be incorrect or unknown; the client will
@@ -1301,6 +1324,7 @@ type announcementMultiQueue struct {
 	commonCompartmentQueues         map[ID]*announcementCompartmentQueue
 	personalCompartmentQueues       map[ID]*announcementCompartmentQueue
 	totalEntries                    int
+	iterator                        *announcementMatchIterator
 }
 
 // announcementCompartmentQueue is a single compartment queue within an
@@ -1325,7 +1349,6 @@ type announcementCompartmentQueue struct {
 type announcementMatchIterator struct {
 	multiQueue        *announcementMultiQueue
 	compartmentQueues []*announcementCompartmentQueue
-	compartmentIDs    []ID
 	nextEntries       []*list.Element
 }
 
@@ -1338,11 +1361,15 @@ type announcementQueueReference struct {
 }
 
 func newAnnouncementMultiQueue() *announcementMultiQueue {
-	return &announcementMultiQueue{
+	q := &announcementMultiQueue{
 		priorityCommonCompartmentQueues: make(map[ID]*announcementCompartmentQueue),
 		commonCompartmentQueues:         make(map[ID]*announcementCompartmentQueue),
 		personalCompartmentQueues:       make(map[ID]*announcementCompartmentQueue),
 	}
+	q.iterator = &announcementMatchIterator{
+		multiQueue: q,
+	}
+	return q
 }
 
 func (q *announcementMultiQueue) getLen() int {
@@ -1471,13 +1498,27 @@ func (r *announcementQueueReference) dequeue() bool {
 	return true
 }
 
+// startMatching returns a newly initialized announcementMatchIterator for
+// queues corresponding to the specified compartment IDs.
+//
+// In order to reduce allocation and garbage collection churn from
+// matchAllOffers/matchOffer repeatedly calling startMatching, a single
+// announcementMatchIterator instance is retained and reused by all
+// startMatching calls. It is not safe to concurrently call startMatching or
+// concurrently use the returned announcementMatchIterator or retain the
+// returned announcementMatchIterator between startMatching calls.
 func (q *announcementMultiQueue) startMatching(
 	isCommonCompartments bool,
 	compartmentIDs []ID) *announcementMatchIterator {
 
-	iter := &announcementMatchIterator{
-		multiQueue: q,
-	}
+	// Reset the reused announcementMatchIterator fields, including clearing
+	// any references, while retaining the allocated slice capacity.
+
+	iter := q.iterator
+	clear(iter.compartmentQueues)
+	iter.compartmentQueues = iter.compartmentQueues[:0]
+	clear(iter.nextEntries)
+	iter.nextEntries = iter.nextEntries[:0]
 
 	// Find the matching compartment queues and initialize iteration over
 	// those queues. Building the set of matching queues is a linear time
@@ -1505,7 +1546,6 @@ func (q *announcementMultiQueue) startMatching(
 		for _, ID := range compartmentIDs {
 			if compartmentQueue, ok := compartmentQueues[ID]; ok {
 				iter.compartmentQueues = append(iter.compartmentQueues, compartmentQueue)
-				iter.compartmentIDs = append(iter.compartmentIDs, ID)
 				iter.nextEntries = append(iter.nextEntries, compartmentQueue.entries.Front())
 			}
 		}
@@ -1569,7 +1609,7 @@ func (iter *announcementMatchIterator) getNext() (*announcementEntry, bool) {
 	// A potential future enhancement is to add more iterator state to track
 	// which queue has the next oldest time to select on the following
 	// getNext call. Another potential enhancement is to remove fully
-	// consumed queues from compartmentQueues/compartmentIDs/nextEntries.
+	// consumed queues from compartmentQueues/nextEntries.
 
 	var selectedCandidate *announcementEntry
 	selectedIndex := -1

+ 5 - 0
psiphon/common/inproxy/proxy.go

@@ -138,6 +138,10 @@ type ProxyConfig struct {
 	// and the provider can select new parameters per connection as reqired.
 	MakeWebRTCDialCoordinator func() (WebRTCDialCoordinator, error)
 
+	// ExcludeInterfaceName specifies the network interface to omit from
+	// proxy WebRTC ICE interface enumeration.
+	ExcludeInterfaceName string
+
 	// HandleTacticsPayload is a callback that receives any tactics payload,
 	// provided by the broker in proxy announcement request responses.
 	// HandleTacticsPayload must return true when the tacticsPayload includes
@@ -1148,6 +1152,7 @@ func (p *Proxy) proxyOneClient(
 		&webRTCConfig{
 			Logger:                      p.config.Logger,
 			EnableDebugLogging:          p.config.EnableWebRTCDebugLogging,
+			ExcludeInterfaceName:        p.config.ExcludeInterfaceName,
 			WebRTCDialCoordinator:       webRTCCoordinator,
 			ClientRootObfuscationSecret: announceResponse.ClientRootObfuscationSecret,
 			DoDTLSRandomization:         announceResponse.DoDTLSRandomization,

+ 38 - 5
psiphon/common/inproxy/webrtc.go

@@ -152,6 +152,10 @@ type webRTCConfig struct {
 	// Logger at a Debug log level.
 	EnableDebugLogging bool
 
+	// ExcludeInterfaceName specifies the interface name to omit from ICE
+	// interface enumeration.
+	ExcludeInterfaceName string
+
 	// WebRTCDialCoordinator specifies specific WebRTC dial strategies and
 	// settings; WebRTCDialCoordinator also facilities dial replay by
 	// receiving callbacks when individual dial steps succeed or fail.
@@ -350,7 +354,10 @@ func newWebRTCConn(
 		config.EnableDebugLogging)
 
 	pionNetwork := newPionNetwork(
-		ctx, pionLoggerFactory.NewLogger("net"), config.WebRTCDialCoordinator)
+		ctx,
+		pionLoggerFactory.NewLogger("net"),
+		config.WebRTCDialCoordinator,
+		config.ExcludeInterfaceName)
 
 	udpMux := webrtc.NewICEUniversalUDPMux(
 		pionLoggerFactory.NewLogger("mux"), udpConn, TTL, pionNetwork)
@@ -2776,6 +2783,7 @@ func filterSDPAddresses(
 // webRTCSDPMetrics are network capability metrics values for an SDP.
 type webRTCSDPMetrics struct {
 	iceCandidateTypes     []ICECandidateType
+	hasIPv4               bool
 	hasIPv6               bool
 	hasPrivateIP          bool
 	filteredICECandidates []string
@@ -2835,6 +2843,7 @@ func processSDPAddresses(
 	}
 
 	candidateTypes := map[ICECandidateType]bool{}
+	hasIPv4 := false
 	hasIPv6 := false
 	hasPrivateIP := false
 	filteredCandidateReasons := make(map[string]int)
@@ -2921,8 +2930,11 @@ func processSDPAddresses(
 					return nil, nil, errors.TraceNew("unexpected non-IP")
 				}
 
+				candidateIsIPv4 := false
 				candidateIsIPv6 := false
-				if candidateIP.To4() == nil {
+				if candidateIP.To4() != nil {
+					candidateIsIPv4 = true
+				} else {
 					if disableIPv6Candidates {
 						reason := fmt.Sprintf("disabled %s IPv6",
 							candidate.Type().String())
@@ -2979,6 +2991,9 @@ func processSDPAddresses(
 				// address, as there could be a mix of IPv4 and IPv6, as well
 				// as potentially different NAT paths.
 				//
+				// For IPv6, only the ASN must match as IPv6 GeoIP country
+				// data appears less reliable in practise.
+				//
 				// In some cases, legitimate clients and proxies may
 				// unintentionally submit candidates with mismatching GeoIP.
 				// This can occur, for example, when a STUN candidate is only
@@ -2992,8 +3007,15 @@ func processSDPAddresses(
 				if lookupGeoIP != nil {
 					candidateGeoIPData := lookupGeoIP(candidate.Address())
 
-					if candidateGeoIPData.Country != expectedGeoIPData.Country ||
-						candidateGeoIPData.ASN != expectedGeoIPData.ASN {
+					mismatch := false
+					if candidateIsIPv6 {
+						mismatch = candidateGeoIPData.ASN != expectedGeoIPData.ASN
+					} else {
+						mismatch = candidateGeoIPData.Country != expectedGeoIPData.Country ||
+							candidateGeoIPData.ASN != expectedGeoIPData.ASN
+					}
+
+					if mismatch {
 
 						version := "IPv4"
 						if candidateIsIPv6 {
@@ -3010,6 +3032,9 @@ func processSDPAddresses(
 					}
 				}
 
+				if candidateIsIPv4 {
+					hasIPv4 = true
+				}
 				if candidateIsIPv6 {
 					hasIPv6 = true
 				}
@@ -3048,6 +3073,7 @@ func processSDPAddresses(
 	}
 
 	metrics := &webRTCSDPMetrics{
+		hasIPv4:      hasIPv4,
 		hasIPv6:      hasIPv6,
 		hasPrivateIP: hasPrivateIP,
 	}
@@ -3236,17 +3262,20 @@ type pionNetwork struct {
 	dialCtx               context.Context
 	logger                pion_logging.LeveledLogger
 	webRTCDialCoordinator WebRTCDialCoordinator
+	excludeInterfaceName  string
 }
 
 func newPionNetwork(
 	dialCtx context.Context,
 	logger pion_logging.LeveledLogger,
-	webRTCDialCoordinator WebRTCDialCoordinator) *pionNetwork {
+	webRTCDialCoordinator WebRTCDialCoordinator,
+	excludeInterfaceName string) *pionNetwork {
 
 	return &pionNetwork{
 		dialCtx:               dialCtx,
 		logger:                logger,
 		webRTCDialCoordinator: webRTCDialCoordinator,
+		excludeInterfaceName:  excludeInterfaceName,
 	}
 }
 
@@ -3308,6 +3337,10 @@ func (p *pionNetwork) Interfaces() ([]*transport.Interface, error) {
 	}
 
 	for _, netInterface := range netInterfaces {
+		if p.excludeInterfaceName != "" && netInterface.Name == p.excludeInterfaceName {
+			continue
+		}
+
 		// Note: don't exclude interfaces with the net.FlagPointToPoint flag,
 		// which is set for certain mobile networks
 		if (netInterface.Flags&net.FlagUp == 0) ||

+ 24 - 0
psiphon/config.go

@@ -31,6 +31,7 @@ import (
 	"path/filepath"
 	"reflect"
 	"regexp"
+	"runtime"
 	"strconv"
 	"strings"
 	"sync"
@@ -658,6 +659,16 @@ type Config struct {
 	// ephemeral key will be generated.
 	InproxyProxySessionPrivateKey string `json:",omitempty"`
 
+	// InproxyProxySplitUpstreamInterfaceName specifies a network interface
+	// that the in-proxy proxy will use for upstream destination dialing. The
+	// specified interface is also excluded from proxy ICE gathering. This is
+	// intended to support split interface setups with the proxy/client
+	// connection on the default interface and the proxy/server connection on
+	// the designated interface.
+	//
+	// Only supported on Linux,  and cannot be used with DeviceBinder.
+	InproxyProxySplitUpstreamInterfaceName string `json:",omitempty"`
+
 	// InproxyMaxClients specifies the maximum number of common in-proxy
 	// clients to be proxied concurrently. When InproxyEnableProxy is set,
 	// it can only be 0 when InProxyMaxPersonalClients is > 0.
@@ -1196,6 +1207,8 @@ type Config struct {
 	ServerEntryIteratorMaxMoveToFront   *int     `json:",omitempty"`
 	ServerEntryIteratorResetProbability *float64 `json:",omitempty"`
 
+	TunnelConnectTimeoutSeconds *int `json:",omitempty"`
+
 	// params is the active parameters.Parameters with defaults, config values,
 	// and, optionally, tactics applied.
 	//
@@ -1572,6 +1585,13 @@ func (config *Config) Commit(migrateFromLegacyFields bool) error {
 		return errors.TraceNew("invalid ObfuscatedSSHAlgorithms")
 	}
 
+	if config.InproxyProxySplitUpstreamInterfaceName != "" && runtime.GOOS != "linux" {
+		return errors.TraceNew("InproxyProxySplitUpstreamInterfaceName is only supported on Linux")
+	}
+	if config.InproxyProxySplitUpstreamInterfaceName != "" && config.DeviceBinder != nil {
+		return errors.TraceNew("InproxyProxySplitUpstreamInterfaceName cannot be used with DeviceBinder")
+	}
+
 	if config.InproxyEnableProxy {
 
 		if config.InproxyMaxCommonClients+config.InproxyMaxPersonalClients <= 0 {
@@ -3085,6 +3105,10 @@ func (config *Config) makeConfigParameters() map[string]interface{} {
 		applyParameters[parameters.EnableDSLFetcher] = *config.EnableDSLFetcher
 	}
 
+	if config.TunnelConnectTimeoutSeconds != nil {
+		applyParameters[parameters.TunnelConnectTimeout] = fmt.Sprintf("%ds", *config.TunnelConnectTimeoutSeconds)
+	}
+
 	// When adding new config dial parameters that may override tactics, also
 	// update setDialParametersHash.
 

+ 1 - 0
psiphon/controller.go

@@ -3368,6 +3368,7 @@ func (controller *Controller) runInproxyProxy() {
 		GetBrokerClient:                      controller.inproxyGetProxyBrokerClient,
 		GetBaseAPIParameters:                 controller.inproxyGetProxyAPIParameters,
 		MakeWebRTCDialCoordinator:            controller.inproxyMakeProxyWebRTCDialCoordinator,
+		ExcludeInterfaceName:                 controller.config.InproxyProxySplitUpstreamInterfaceName,
 		HandleTacticsPayload:                 controller.inproxyHandleProxyTacticsPayload,
 		MaxCommonClients:                     controller.config.InproxyMaxCommonClients,
 		MaxPersonalClients:                   controller.config.InproxyMaxPersonalClients,

+ 6 - 4
psiphon/dataStoreRecovery_test.go

@@ -58,8 +58,10 @@ func TestBoltResiliency(t *testing.T) {
         "ClientVersion" : "0000000000000000",
         "SponsorId" : "0000000000000000",
         "PropagationChannelId" : "0",
-        "ConnectionWorkerPoolSize" : 10,
-        "EstablishTunnelTimeoutSeconds" : 1,
+        "DisableTactics" : true,
+        "ConnectionWorkerPoolSize" : 1,
+        "TunnelConnectTimeoutSeconds" : 1,
+        "EstablishTunnelTimeoutSeconds" : 0,
         "EstablishTunnelPausePeriodSeconds" : 1
     }`
 
@@ -107,7 +109,7 @@ func TestBoltResiliency(t *testing.T) {
 				case noticeExiting <- struct{}{}:
 				default:
 				}
-			case "Alert":
+			case "Warning":
 				message := payload["message"].(string)
 				var channel chan struct{}
 				if strings.Contains(message, "tryDatastoreOpenDB: reset") {
@@ -135,7 +137,7 @@ func TestBoltResiliency(t *testing.T) {
 	drainNoticeChannel := func(channel chan struct{}) {
 		for {
 			select {
-			case channel <- struct{}{}:
+			case <-channel:
 			default:
 				return
 			}

+ 1 - 1
psiphon/dataStore_bolt.go

@@ -222,7 +222,7 @@ func (db *datastoreDB) isDatastoreFailed() bool {
 
 func (db *datastoreDB) setDatastoreFailed(r interface{}) {
 	atomic.StoreInt32(&db.isFailed, 1)
-	NoticeWarning("Datastore failed: %s", errors.Tracef("panic: %v", r))
+	NoticeWarning("%s: %s", errDatastoreFailed.Error(), errors.Tracef("panic: %v", r))
 }
 
 func (db *datastoreDB) close() error {

+ 9 - 0
psiphon/inproxy.go

@@ -41,6 +41,7 @@ import (
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/parameters"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/resolver"
+	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/tun"
 	utls "github.com/Psiphon-Labs/utls"
 	"github.com/cespare/xxhash"
 )
@@ -2003,6 +2004,8 @@ func (w *InproxyWebRTCDialInstance) ProxyUpstreamDial(
 	// DNSResolverPreresolvedIPAddressCIDRs proxy tactics. In addition,
 	// replay the selected upstream dial tactics parameters.
 
+	splitUpstreamInterfaceName := w.config.InproxyProxySplitUpstreamInterfaceName
+
 	dialer := net.Dialer{
 		Control: func(_, _ string, c syscall.RawConn) error {
 			var controlErr error
@@ -2018,6 +2021,12 @@ func (w *InproxyWebRTCDialInstance) ProxyUpstreamDial(
 						controlErr = errors.Tracef("BindToDevice failed: %s", err)
 						return
 					}
+				} else if splitUpstreamInterfaceName != "" {
+					err := tun.BindToDevice(socketFD, splitUpstreamInterfaceName)
+					if err != nil {
+						controlErr = errors.Tracef("BindToDevice failed: %s", err)
+						return
+					}
 				}
 			})
 			if controlErr != nil {

+ 1 - 1
psiphon/notice.go

@@ -1465,7 +1465,7 @@ func (log *commonLogTrace) Info(args ...interface{}) {
 }
 
 func (log *commonLogTrace) Warning(args ...interface{}) {
-	log.outputNotice("Alert", args...)
+	log.outputNotice("Warning", args...)
 }
 
 func (log *commonLogTrace) Error(args ...interface{}) {

+ 43 - 3
psiphon/server/pb/psiphond/inproxy_broker.pb.go

@@ -64,6 +64,10 @@ type InproxyBroker struct {
 	MeekServerHttpVersion         *string                `protobuf:"bytes,137,opt,name=meek_server_http_version,json=meekServerHttpVersion,proto3,oneof" json:"meek_server_http_version,omitempty"`
 	PendingAnswersSize            *int64                 `protobuf:"varint,138,opt,name=pending_answers_size,json=pendingAnswersSize,proto3,oneof" json:"pending_answers_size,omitempty"`
 	OfferDeadline                 *int64                 `protobuf:"varint,139,opt,name=offer_deadline,json=offerDeadline,proto3,oneof" json:"offer_deadline,omitempty"`
+	MaxCommonClients              *int64                 `protobuf:"varint,140,opt,name=max_common_clients,json=maxCommonClients,proto3,oneof" json:"max_common_clients,omitempty"`
+	MaxPersonalClients            *int64                 `protobuf:"varint,141,opt,name=max_personal_clients,json=maxPersonalClients,proto3,oneof" json:"max_personal_clients,omitempty"`
+	Has_IPv4                      *bool                  `protobuf:"varint,142,opt,name=has_IPv4,json=hasIPv4,proto3,oneof" json:"has_IPv4,omitempty"`
+	MatchDuration                 *int64                 `protobuf:"varint,143,opt,name=match_duration,json=matchDuration,proto3,oneof" json:"match_duration,omitempty"`
 	unknownFields                 protoimpl.UnknownFields
 	sizeCache                     protoimpl.SizeCache
 }
@@ -385,11 +389,39 @@ func (x *InproxyBroker) GetOfferDeadline() int64 {
 	return 0
 }
 
+func (x *InproxyBroker) GetMaxCommonClients() int64 {
+	if x != nil && x.MaxCommonClients != nil {
+		return *x.MaxCommonClients
+	}
+	return 0
+}
+
+func (x *InproxyBroker) GetMaxPersonalClients() int64 {
+	if x != nil && x.MaxPersonalClients != nil {
+		return *x.MaxPersonalClients
+	}
+	return 0
+}
+
+func (x *InproxyBroker) GetHas_IPv4() bool {
+	if x != nil && x.Has_IPv4 != nil {
+		return *x.Has_IPv4
+	}
+	return false
+}
+
+func (x *InproxyBroker) GetMatchDuration() int64 {
+	if x != nil && x.MatchDuration != nil {
+		return *x.MatchDuration
+	}
+	return 0
+}
+
 var File_ca_psiphon_psiphond_inproxy_broker_proto protoreflect.FileDescriptor
 
 const file_ca_psiphon_psiphond_inproxy_broker_proto_rawDesc = "" +
 	"\n" +
-	"(ca.psiphon.psiphond/inproxy_broker.proto\x12\x13ca.psiphon.psiphond\x1a%ca.psiphon.psiphond/base_params.proto\"\xd3\x16\n" +
+	"(ca.psiphon.psiphond/inproxy_broker.proto\x12\x13ca.psiphon.psiphond\x1a%ca.psiphon.psiphond/base_params.proto\"\xdd\x18\n" +
 	"\rInproxyBroker\x12E\n" +
 	"\vbase_params\x18\x01 \x01(\v2\x1f.ca.psiphon.psiphond.BaseParamsH\x00R\n" +
 	"baseParams\x88\x01\x01\x12=\n" +
@@ -435,7 +467,11 @@ const file_ca_psiphon_psiphond_inproxy_broker_proto_rawDesc = "" +
 	"\ttimed_out\x18\x88\x01 \x01(\bH!R\btimedOut\x88\x01\x01\x12=\n" +
 	"\x18meek_server_http_version\x18\x89\x01 \x01(\tH\"R\x15meekServerHttpVersion\x88\x01\x01\x126\n" +
 	"\x14pending_answers_size\x18\x8a\x01 \x01(\x03H#R\x12pendingAnswersSize\x88\x01\x01\x12+\n" +
-	"\x0eoffer_deadline\x18\x8b\x01 \x01(\x03H$R\rofferDeadline\x88\x01\x01B\x0e\n" +
+	"\x0eoffer_deadline\x18\x8b\x01 \x01(\x03H$R\rofferDeadline\x88\x01\x01\x122\n" +
+	"\x12max_common_clients\x18\x8c\x01 \x01(\x03H%R\x10maxCommonClients\x88\x01\x01\x126\n" +
+	"\x14max_personal_clients\x18\x8d\x01 \x01(\x03H&R\x12maxPersonalClients\x88\x01\x01\x12\x1f\n" +
+	"\bhas_IPv4\x18\x8e\x01 \x01(\bH'R\ahasIPv4\x88\x01\x01\x12+\n" +
+	"\x0ematch_duration\x18\x8f\x01 \x01(\x03H(R\rmatchDuration\x88\x01\x01B\x0e\n" +
 	"\f_base_paramsB\x1b\n" +
 	"\x19_announcement_match_indexB\x1a\n" +
 	"\x18_announcement_queue_sizeB\x0f\n" +
@@ -475,7 +511,11 @@ const file_ca_psiphon_psiphond_inproxy_broker_proto_rawDesc = "" +
 	"_timed_outB\x1b\n" +
 	"\x19_meek_server_http_versionB\x17\n" +
 	"\x15_pending_answers_sizeB\x11\n" +
-	"\x0f_offer_deadlineBHZFgithub.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/server/pb/psiphondb\x06proto3"
+	"\x0f_offer_deadlineB\x15\n" +
+	"\x13_max_common_clientsB\x17\n" +
+	"\x15_max_personal_clientsB\v\n" +
+	"\t_has_IPv4B\x11\n" +
+	"\x0f_match_durationBHZFgithub.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/server/pb/psiphondb\x06proto3"
 
 var (
 	file_ca_psiphon_psiphond_inproxy_broker_proto_rawDescOnce sync.Once

+ 23 - 3
psiphon/server/pb/psiphond/inproxy_dial_params.pb.go

@@ -112,6 +112,8 @@ type InproxyDialParams struct {
 	InproxyWebrtcStunServer_RFC5780ResolvedIpAddress *string                `protobuf:"bytes,87,opt,name=inproxy_webrtc_stun_server_RFC5780_resolved_ip_address,json=inproxyWebrtcStunServerRFC5780ResolvedIpAddress,proto3,oneof" json:"inproxy_webrtc_stun_server_RFC5780_resolved_ip_address,omitempty"`
 	InproxyWebrtcStunServerResolvedIpAddress         *string                `protobuf:"bytes,88,opt,name=inproxy_webrtc_stun_server_resolved_ip_address,json=inproxyWebrtcStunServerResolvedIpAddress,proto3,oneof" json:"inproxy_webrtc_stun_server_resolved_ip_address,omitempty"`
 	InproxyWebrtcUseMediaStreams                     *bool                  `protobuf:"varint,89,opt,name=inproxy_webrtc_use_media_streams,json=inproxyWebrtcUseMediaStreams,proto3,oneof" json:"inproxy_webrtc_use_media_streams,omitempty"`
+	InproxyProxyMaxCommonClients                     *int64                 `protobuf:"varint,90,opt,name=inproxy_proxy_max_common_clients,json=inproxyProxyMaxCommonClients,proto3,oneof" json:"inproxy_proxy_max_common_clients,omitempty"`
+	InproxyProxyMaxPersonalClients                   *int64                 `protobuf:"varint,91,opt,name=inproxy_proxy_max_personal_clients,json=inproxyProxyMaxPersonalClients,proto3,oneof" json:"inproxy_proxy_max_personal_clients,omitempty"`
 	unknownFields                                    protoimpl.UnknownFields
 	sizeCache                                        protoimpl.SizeCache
 }
@@ -769,11 +771,25 @@ func (x *InproxyDialParams) GetInproxyWebrtcUseMediaStreams() bool {
 	return false
 }
 
+func (x *InproxyDialParams) GetInproxyProxyMaxCommonClients() int64 {
+	if x != nil && x.InproxyProxyMaxCommonClients != nil {
+		return *x.InproxyProxyMaxCommonClients
+	}
+	return 0
+}
+
+func (x *InproxyDialParams) GetInproxyProxyMaxPersonalClients() int64 {
+	if x != nil && x.InproxyProxyMaxPersonalClients != nil {
+		return *x.InproxyProxyMaxPersonalClients
+	}
+	return 0
+}
+
 var File_ca_psiphon_psiphond_inproxy_dial_params_proto protoreflect.FileDescriptor
 
 const file_ca_psiphon_psiphond_inproxy_dial_params_proto_rawDesc = "" +
 	"\n" +
-	"-ca.psiphon.psiphond/inproxy_dial_params.proto\x12\x13ca.psiphon.psiphond\"\xf1M\n" +
+	"-ca.psiphon.psiphond/inproxy_dial_params.proto\x12\x13ca.psiphon.psiphond\"\xdbO\n" +
 	"\x11InproxyDialParams\x12>\n" +
 	"\x19inproxy_broker_client_bpf\x18\x01 \x01(\tH\x00R\x16inproxyBrokerClientBpf\x88\x01\x01\x12B\n" +
 	"\x1binproxy_broker_dial_address\x18\x02 \x01(\tH\x01R\x18inproxyBrokerDialAddress\x88\x01\x01\x12@\n" +
@@ -865,7 +881,9 @@ const file_ca_psiphon_psiphond_inproxy_dial_params_proto_rawDesc = "" +
 	"\"inproxy_webrtc_stun_server_RFC5780\x18V \x01(\tHRR\x1einproxyWebrtcStunServerRFC5780\x88\x01\x01\x12t\n" +
 	"6inproxy_webrtc_stun_server_RFC5780_resolved_ip_address\x18W \x01(\tHSR/inproxyWebrtcStunServerRFC5780ResolvedIpAddress\x88\x01\x01\x12e\n" +
 	".inproxy_webrtc_stun_server_resolved_ip_address\x18X \x01(\tHTR(inproxyWebrtcStunServerResolvedIpAddress\x88\x01\x01\x12K\n" +
-	" inproxy_webrtc_use_media_streams\x18Y \x01(\bHUR\x1cinproxyWebrtcUseMediaStreams\x88\x01\x01B\x1c\n" +
+	" inproxy_webrtc_use_media_streams\x18Y \x01(\bHUR\x1cinproxyWebrtcUseMediaStreams\x88\x01\x01\x12K\n" +
+	" inproxy_proxy_max_common_clients\x18Z \x01(\x03HVR\x1cinproxyProxyMaxCommonClients\x88\x01\x01\x12O\n" +
+	"\"inproxy_proxy_max_personal_clients\x18[ \x01(\x03HWR\x1einproxyProxyMaxPersonalClients\x88\x01\x01B\x1c\n" +
 	"\x1a_inproxy_broker_client_bpfB\x1e\n" +
 	"\x1c_inproxy_broker_dial_addressB\x1d\n" +
 	"\x1b_inproxy_broker_dns_attemptB\x1f\n" +
@@ -951,7 +969,9 @@ const file_ca_psiphon_psiphond_inproxy_dial_params_proto_rawDesc = "" +
 	"#_inproxy_webrtc_stun_server_RFC5780B9\n" +
 	"7_inproxy_webrtc_stun_server_RFC5780_resolved_ip_addressB1\n" +
 	"/_inproxy_webrtc_stun_server_resolved_ip_addressB#\n" +
-	"!_inproxy_webrtc_use_media_streamsBHZFgithub.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/server/pb/psiphondb\x06proto3"
+	"!_inproxy_webrtc_use_media_streamsB#\n" +
+	"!_inproxy_proxy_max_common_clientsB%\n" +
+	"#_inproxy_proxy_max_personal_clientsBHZFgithub.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/server/pb/psiphondb\x06proto3"
 
 var (
 	file_ca_psiphon_psiphond_inproxy_dial_params_proto_rawDescOnce sync.Once

binární
psiphon/server/pb/psiphond/psiphond.desc


binární
psiphon/server/pb/router/router.desc


+ 4 - 0
psiphon/server/proto/ca.psiphon.psiphond/inproxy_broker.proto

@@ -51,4 +51,8 @@ message InproxyBroker {
     optional string meek_server_http_version = 137;
     optional int64 pending_answers_size = 138;
     optional int64 offer_deadline = 139;
+    optional int64 max_common_clients = 140;
+    optional int64 max_personal_clients = 141;
+    optional bool has_IPv4 = 142;
+    optional int64 match_duration = 143;
 }

+ 2 - 1
psiphon/server/proto/ca.psiphon.psiphond/inproxy_dial_params.proto

@@ -94,5 +94,6 @@ message InproxyDialParams {
     optional string inproxy_webrtc_stun_server_RFC5780_resolved_ip_address = 87;
     optional string inproxy_webrtc_stun_server_resolved_ip_address = 88;
     optional bool inproxy_webrtc_use_media_streams = 89;
+    optional int64 inproxy_proxy_max_common_clients = 90;
+    optional int64 inproxy_proxy_max_personal_clients = 91;
 }
-

+ 1 - 1
psiphon/server/proto/compile_protobufs.sh

@@ -14,5 +14,5 @@ find . -mindepth 1 -maxdepth 1 -type d -name 'ca.psiphon.*' | sed -e 's|^\./||'
   pkg="${src##*.}"
 
   mkdir -p "../pb/${pkg}" || fatal "failed to create compiled protobuf directory: ../pb/${pkg}"
-  protoc --go_out="../pb/${pkg}/" --go_opt="module=github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/server/pb/${pkg}" "${src}/"*.proto
+  protoc --go_out="../pb/${pkg}/" --go_opt="module=github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/server/pb/${pkg}" --descriptor_set_out="../pb/${pkg}/${pkg}.desc" --include_imports "${src}/"*.proto
 done

+ 0 - 1
psiphon/server/protobufConverter.go

@@ -95,7 +95,6 @@ func NewProtobufRoutedMessage(
 	return &pbr.Router{
 		Destination: &destination,
 		MessageType: &messageType,
-		Key:         []byte(logHostID),
 		Value:       serialized,
 	}, nil
 }

+ 2 - 0
psiphon/server/server_test.go

@@ -3277,6 +3277,8 @@ func checkExpectedServerTunnelLogFields(
 			"inproxy_proxy_network_type",
 			"inproxy_proxy_protocol_version",
 			"inproxy_proxy_nat_type",
+			"inproxy_proxy_max_common_clients",
+			"inproxy_proxy_max_personal_clients",
 			"inproxy_proxy_max_clients",
 			"inproxy_proxy_connecting_clients",
 			"inproxy_proxy_connected_clients",

+ 4 - 2
vendor/filippo.io/edwards25519/README.md

@@ -7,8 +7,10 @@ import "filippo.io/edwards25519"
 This library implements the edwards25519 elliptic curve, exposing the necessary APIs to build a wide array of higher-level primitives.
 Read the docs at [pkg.go.dev/filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519).
 
-The code is originally derived from Adam Langley's internal implementation in the Go standard library, and includes George Tankersley's [performance improvements](https://golang.org/cl/71950). It was then further developed by Henry de Valence for use in ristretto255, and was finally [merged back into the Go standard library](https://golang.org/cl/276272) as of Go 1.17. It now tracks the upstream codebase and extends it with additional functionality.
+The package tracks the upstream standard library package `crypto/internal/fips140/edwards25519` and extends it with additional functionality.
 
-Most users don't need this package, and should instead use `crypto/ed25519` for signatures, `golang.org/x/crypto/curve25519` for Diffie-Hellman, or `github.com/gtank/ristretto255` for prime order group logic. However, for anyone currently using a fork of `crypto/internal/edwards25519`/`crypto/ed25519/internal/edwards25519` or `github.com/agl/edwards25519`, this package should be a safer, faster, and more powerful alternative.
+The code is originally derived from Adam Langley's internal implementation in the Go standard library, and includes George Tankersley's [performance improvements](https://golang.org/cl/71950). It was then further developed by Henry de Valence for use in ristretto255, and was finally [merged back into the Go standard library](https://golang.org/cl/276272) as of Go 1.17.
+
+Most users don't need this package, and should instead use `crypto/ed25519` for signatures, `crypto/ecdh` for Diffie-Hellman, or `github.com/gtank/ristretto255` for prime order group logic. However, for anyone currently using a fork of the internal `edwards25519` package or of `github.com/agl/edwards25519`, this package should be a safer, faster, and more powerful alternative.
 
 Since this package is meant to curb proliferation of edwards25519 implementations in the Go ecosystem, it welcomes requests for new APIs or reviewable performance improvements.

+ 3 - 3
vendor/filippo.io/edwards25519/doc.go

@@ -10,11 +10,11 @@
 // the curve used by the Ed25519 signature scheme.
 //
 // Most users don't need this package, and should instead use crypto/ed25519 for
-// signatures, golang.org/x/crypto/curve25519 for Diffie-Hellman, or
-// github.com/gtank/ristretto255 for prime order group logic.
+// signatures, crypto/ecdh for Diffie-Hellman, or github.com/gtank/ristretto255
+// for prime order group logic.
 //
 // However, developers who do need to interact with low-level edwards25519
 // operations can use this package, which is an extended version of
-// crypto/internal/edwards25519 from the standard library repackaged as
+// crypto/internal/fips140/edwards25519 from the standard library repackaged as
 // an importable module.
 package edwards25519

+ 60 - 8
vendor/filippo.io/edwards25519/extra.go

@@ -9,6 +9,7 @@ package edwards25519
 
 import (
 	"errors"
+	"slices"
 
 	"filippo.io/edwards25519/field"
 )
@@ -100,13 +101,15 @@ func (v *Point) bytesMontgomery(buf *[32]byte) []byte {
 	//
 	//              u = (1 + y) / (1 - y)
 	//
-	// where y = Y / Z.
+	// where y = Y / Z and therefore
+	//
+	//              u = (Z + Y) / (Z - Y)
 
-	var y, recip, u field.Element
+	var n, r, u field.Element
 
-	y.Multiply(&v.y, y.Invert(&v.z))        // y = Y / Z
-	recip.Invert(recip.Subtract(feOne, &y)) // r = 1/(1 - y)
-	u.Multiply(u.Add(feOne, &y), &recip)    // u = (1 + y)*r
+	n.Add(&v.z, &v.y)                // n = Z + Y
+	r.Invert(r.Subtract(&v.z, &v.y)) // r = 1 / (Z - Y)
+	u.Multiply(&n, &r)               // u = n * r
 
 	return copyFieldElement(buf, &u)
 }
@@ -124,7 +127,7 @@ func (v *Point) MultByCofactor(p *Point) *Point {
 	return v.fromP1xP1(&result)
 }
 
-// Given k > 0, set s = s**(2*i).
+// Given k > 0, set s = s**(2*k).
 func (s *Scalar) pow2k(k int) {
 	for i := 0; i < k; i++ {
 		s.Multiply(s, s)
@@ -250,12 +253,14 @@ func (v *Point) MultiScalarMult(scalars []*Scalar, points []*Point) *Point {
 	// between each point in the multiscalar equation.
 
 	// Build lookup tables for each point
-	tables := make([]projLookupTable, len(points))
+	tables := make([]projLookupTable, 0, 2) // avoid allocation for small sizes
+	tables = slices.Grow(tables, len(points))[:len(points)]
 	for i := range tables {
 		tables[i].FromP3(points[i])
 	}
 	// Compute signed radix-16 digits for each scalar
-	digits := make([][64]int8, len(scalars))
+	digits := make([][64]int8, 0, 2) // avoid allocation for small sizes
+	digits = slices.Grow(digits, len(scalars))[:len(scalars)]
 	for i := range digits {
 		digits[i] = scalars[i].signedRadix16()
 	}
@@ -265,6 +270,7 @@ func (v *Point) MultiScalarMult(scalars []*Scalar, points []*Point) *Point {
 	tmp1 := &projP1xP1{}
 	tmp2 := &projP2{}
 	// Lookup-and-add the appropriate multiple of each input point
+	v.Set(NewIdentityPoint())
 	for j := range tables {
 		tables[j].SelectInto(multiple, digits[j][63])
 		tmp1.Add(v, multiple) // tmp1 = v + x_(j,63)*Q in P1xP1 coords
@@ -347,3 +353,49 @@ func (v *Point) VarTimeMultiScalarMult(scalars []*Scalar, points []*Point) *Poin
 	v.fromP2(tmp2)
 	return v
 }
+
+// Select sets v to a if cond == 1 and to b if cond == 0.
+func (v *Point) Select(a, b *Point, cond int) *Point {
+	checkInitialized(a, b)
+	v.x.Select(&a.x, &b.x, cond)
+	v.y.Select(&a.y, &b.y, cond)
+	v.z.Select(&a.z, &b.z, cond)
+	v.t.Select(&a.t, &b.t, cond)
+	return v
+}
+
+// Double sets v = p + p, and returns v.
+func (v *Point) Double(p *Point) *Point {
+	checkInitialized(p)
+
+	pp := new(projP2).FromP3(p)
+	p1 := new(projP1xP1).Double(pp)
+	return v.fromP1xP1(p1)
+}
+
+func (v *Point) addCached(p *Point, qCached *projCached) *Point {
+	result := new(projP1xP1).Add(p, qCached)
+	return v.fromP1xP1(result)
+}
+
+// ScalarMultSlow sets v = x * q, and returns v. It doesn't precompute a large
+// table, so it is considerably slower, but requires less memory.
+//
+// The scalar multiplication is done in constant time.
+func (v *Point) ScalarMultSlow(x *Scalar, q *Point) *Point {
+	checkInitialized(q)
+
+	s := x.Bytes()
+	qCached := new(projCached).FromP3(q)
+	v.Set(NewIdentityPoint())
+	t := new(Point)
+
+	for i := 255; i >= 0; i-- {
+		v.Double(v)
+		t.addCached(v, qCached)
+		cond := (s[i/8] >> (i % 8)) & 1
+		v.Select(t, v, int(cond))
+	}
+
+	return v
+}

+ 17 - 17
vendor/filippo.io/edwards25519/field/fe.go

@@ -90,11 +90,7 @@ func (v *Element) Add(a, b *Element) *Element {
 	v.l2 = a.l2 + b.l2
 	v.l3 = a.l3 + b.l3
 	v.l4 = a.l4 + b.l4
-	// Using the generic implementation here is actually faster than the
-	// assembly. Probably because the body of this function is so simple that
-	// the compiler can figure out better optimizations by inlining the carry
-	// propagation.
-	return v.carryPropagateGeneric()
+	return v.carryPropagate()
 }
 
 // Subtract sets v = a - b, and returns v.
@@ -232,18 +228,22 @@ func (v *Element) bytes(out *[32]byte) []byte {
 	t := *v
 	t.reduce()
 
-	var buf [8]byte
-	for i, l := range [5]uint64{t.l0, t.l1, t.l2, t.l3, t.l4} {
-		bitsOffset := i * 51
-		binary.LittleEndian.PutUint64(buf[:], l<<uint(bitsOffset%8))
-		for i, bb := range buf {
-			off := bitsOffset/8 + i
-			if off >= len(out) {
-				break
-			}
-			out[off] |= bb
-		}
-	}
+	// Pack five 51-bit limbs into four 64-bit words:
+	//
+	//  255    204    153    102     51      0
+	//    ├──l4──┼──l3──┼──l2──┼──l1──┼──l0──┤
+	//   ├───u3───┼───u2───┼───u1───┼───u0───┤
+	// 256      192      128       64        0
+
+	u0 := t.l1<<51 | t.l0
+	u1 := t.l2<<(102-64) | t.l1>>(64-51)
+	u2 := t.l3<<(153-128) | t.l2>>(128-102)
+	u3 := t.l4<<(204-192) | t.l3>>(192-153)
+
+	binary.LittleEndian.PutUint64(out[0*8:], u0)
+	binary.LittleEndian.PutUint64(out[1*8:], u1)
+	binary.LittleEndian.PutUint64(out[2*8:], u2)
+	binary.LittleEndian.PutUint64(out[3*8:], u3)
 
 	return out[:]
 }

+ 1 - 2
vendor/filippo.io/edwards25519/field/fe_amd64.go

@@ -1,7 +1,6 @@
 // Code generated by command: go run fe_amd64_asm.go -out ../fe_amd64.s -stubs ../fe_amd64.go -pkg field. DO NOT EDIT.
 
-//go:build amd64 && gc && !purego
-// +build amd64,gc,!purego
+//go:build !purego
 
 package field
 

+ 111 - 92
vendor/filippo.io/edwards25519/field/fe_amd64.s

@@ -1,7 +1,6 @@
 // Code generated by command: go run fe_amd64_asm.go -out ../fe_amd64.s -stubs ../fe_amd64.go -pkg field. DO NOT EDIT.
 
-//go:build amd64 && gc && !purego
-// +build amd64,gc,!purego
+//go:build !purego
 
 #include "textflag.h"
 
@@ -17,32 +16,36 @@ TEXT ·feMul(SB), NOSPLIT, $0-24
 	MOVQ DX, SI
 
 	// r0 += 19×a1×b4
-	MOVQ   8(CX), AX
-	IMUL3Q $0x13, AX, AX
-	MULQ   32(BX)
-	ADDQ   AX, DI
-	ADCQ   DX, SI
+	MOVQ 8(CX), DX
+	LEAQ (DX)(DX*8), AX
+	LEAQ (DX)(AX*2), AX
+	MULQ 32(BX)
+	ADDQ AX, DI
+	ADCQ DX, SI
 
 	// r0 += 19×a2×b3
-	MOVQ   16(CX), AX
-	IMUL3Q $0x13, AX, AX
-	MULQ   24(BX)
-	ADDQ   AX, DI
-	ADCQ   DX, SI
+	MOVQ 16(CX), DX
+	LEAQ (DX)(DX*8), AX
+	LEAQ (DX)(AX*2), AX
+	MULQ 24(BX)
+	ADDQ AX, DI
+	ADCQ DX, SI
 
 	// r0 += 19×a3×b2
-	MOVQ   24(CX), AX
-	IMUL3Q $0x13, AX, AX
-	MULQ   16(BX)
-	ADDQ   AX, DI
-	ADCQ   DX, SI
+	MOVQ 24(CX), DX
+	LEAQ (DX)(DX*8), AX
+	LEAQ (DX)(AX*2), AX
+	MULQ 16(BX)
+	ADDQ AX, DI
+	ADCQ DX, SI
 
 	// r0 += 19×a4×b1
-	MOVQ   32(CX), AX
-	IMUL3Q $0x13, AX, AX
-	MULQ   8(BX)
-	ADDQ   AX, DI
-	ADCQ   DX, SI
+	MOVQ 32(CX), DX
+	LEAQ (DX)(DX*8), AX
+	LEAQ (DX)(AX*2), AX
+	MULQ 8(BX)
+	ADDQ AX, DI
+	ADCQ DX, SI
 
 	// r1 = a0×b1
 	MOVQ (CX), AX
@@ -57,25 +60,28 @@ TEXT ·feMul(SB), NOSPLIT, $0-24
 	ADCQ DX, R8
 
 	// r1 += 19×a2×b4
-	MOVQ   16(CX), AX
-	IMUL3Q $0x13, AX, AX
-	MULQ   32(BX)
-	ADDQ   AX, R9
-	ADCQ   DX, R8
+	MOVQ 16(CX), DX
+	LEAQ (DX)(DX*8), AX
+	LEAQ (DX)(AX*2), AX
+	MULQ 32(BX)
+	ADDQ AX, R9
+	ADCQ DX, R8
 
 	// r1 += 19×a3×b3
-	MOVQ   24(CX), AX
-	IMUL3Q $0x13, AX, AX
-	MULQ   24(BX)
-	ADDQ   AX, R9
-	ADCQ   DX, R8
+	MOVQ 24(CX), DX
+	LEAQ (DX)(DX*8), AX
+	LEAQ (DX)(AX*2), AX
+	MULQ 24(BX)
+	ADDQ AX, R9
+	ADCQ DX, R8
 
 	// r1 += 19×a4×b2
-	MOVQ   32(CX), AX
-	IMUL3Q $0x13, AX, AX
-	MULQ   16(BX)
-	ADDQ   AX, R9
-	ADCQ   DX, R8
+	MOVQ 32(CX), DX
+	LEAQ (DX)(DX*8), AX
+	LEAQ (DX)(AX*2), AX
+	MULQ 16(BX)
+	ADDQ AX, R9
+	ADCQ DX, R8
 
 	// r2 = a0×b2
 	MOVQ (CX), AX
@@ -96,18 +102,20 @@ TEXT ·feMul(SB), NOSPLIT, $0-24
 	ADCQ DX, R10
 
 	// r2 += 19×a3×b4
-	MOVQ   24(CX), AX
-	IMUL3Q $0x13, AX, AX
-	MULQ   32(BX)
-	ADDQ   AX, R11
-	ADCQ   DX, R10
+	MOVQ 24(CX), DX
+	LEAQ (DX)(DX*8), AX
+	LEAQ (DX)(AX*2), AX
+	MULQ 32(BX)
+	ADDQ AX, R11
+	ADCQ DX, R10
 
 	// r2 += 19×a4×b3
-	MOVQ   32(CX), AX
-	IMUL3Q $0x13, AX, AX
-	MULQ   24(BX)
-	ADDQ   AX, R11
-	ADCQ   DX, R10
+	MOVQ 32(CX), DX
+	LEAQ (DX)(DX*8), AX
+	LEAQ (DX)(AX*2), AX
+	MULQ 24(BX)
+	ADDQ AX, R11
+	ADCQ DX, R10
 
 	// r3 = a0×b3
 	MOVQ (CX), AX
@@ -134,11 +142,12 @@ TEXT ·feMul(SB), NOSPLIT, $0-24
 	ADCQ DX, R12
 
 	// r3 += 19×a4×b4
-	MOVQ   32(CX), AX
-	IMUL3Q $0x13, AX, AX
-	MULQ   32(BX)
-	ADDQ   AX, R13
-	ADCQ   DX, R12
+	MOVQ 32(CX), DX
+	LEAQ (DX)(DX*8), AX
+	LEAQ (DX)(AX*2), AX
+	MULQ 32(BX)
+	ADDQ AX, R13
+	ADCQ DX, R12
 
 	// r4 = a0×b4
 	MOVQ (CX), AX
@@ -232,18 +241,22 @@ TEXT ·feSquare(SB), NOSPLIT, $0-16
 	MOVQ DX, BX
 
 	// r0 += 38×l1×l4
-	MOVQ   8(CX), AX
-	IMUL3Q $0x26, AX, AX
-	MULQ   32(CX)
-	ADDQ   AX, SI
-	ADCQ   DX, BX
+	MOVQ 8(CX), DX
+	LEAQ (DX)(DX*8), AX
+	LEAQ (DX)(AX*2), AX
+	SHLQ $0x01, AX
+	MULQ 32(CX)
+	ADDQ AX, SI
+	ADCQ DX, BX
 
 	// r0 += 38×l2×l3
-	MOVQ   16(CX), AX
-	IMUL3Q $0x26, AX, AX
-	MULQ   24(CX)
-	ADDQ   AX, SI
-	ADCQ   DX, BX
+	MOVQ 16(CX), DX
+	LEAQ (DX)(DX*8), AX
+	LEAQ (DX)(AX*2), AX
+	SHLQ $0x01, AX
+	MULQ 24(CX)
+	ADDQ AX, SI
+	ADCQ DX, BX
 
 	// r1 = 2×l0×l1
 	MOVQ (CX), AX
@@ -253,18 +266,21 @@ TEXT ·feSquare(SB), NOSPLIT, $0-16
 	MOVQ DX, DI
 
 	// r1 += 38×l2×l4
-	MOVQ   16(CX), AX
-	IMUL3Q $0x26, AX, AX
-	MULQ   32(CX)
-	ADDQ   AX, R8
-	ADCQ   DX, DI
+	MOVQ 16(CX), DX
+	LEAQ (DX)(DX*8), AX
+	LEAQ (DX)(AX*2), AX
+	SHLQ $0x01, AX
+	MULQ 32(CX)
+	ADDQ AX, R8
+	ADCQ DX, DI
 
 	// r1 += 19×l3×l3
-	MOVQ   24(CX), AX
-	IMUL3Q $0x13, AX, AX
-	MULQ   24(CX)
-	ADDQ   AX, R8
-	ADCQ   DX, DI
+	MOVQ 24(CX), DX
+	LEAQ (DX)(DX*8), AX
+	LEAQ (DX)(AX*2), AX
+	MULQ 24(CX)
+	ADDQ AX, R8
+	ADCQ DX, DI
 
 	// r2 = 2×l0×l2
 	MOVQ (CX), AX
@@ -280,11 +296,13 @@ TEXT ·feSquare(SB), NOSPLIT, $0-16
 	ADCQ DX, R9
 
 	// r2 += 38×l3×l4
-	MOVQ   24(CX), AX
-	IMUL3Q $0x26, AX, AX
-	MULQ   32(CX)
-	ADDQ   AX, R10
-	ADCQ   DX, R9
+	MOVQ 24(CX), DX
+	LEAQ (DX)(DX*8), AX
+	LEAQ (DX)(AX*2), AX
+	SHLQ $0x01, AX
+	MULQ 32(CX)
+	ADDQ AX, R10
+	ADCQ DX, R9
 
 	// r3 = 2×l0×l3
 	MOVQ (CX), AX
@@ -294,18 +312,19 @@ TEXT ·feSquare(SB), NOSPLIT, $0-16
 	MOVQ DX, R11
 
 	// r3 += 2×l1×l2
-	MOVQ   8(CX), AX
-	IMUL3Q $0x02, AX, AX
-	MULQ   16(CX)
-	ADDQ   AX, R12
-	ADCQ   DX, R11
+	MOVQ 8(CX), AX
+	SHLQ $0x01, AX
+	MULQ 16(CX)
+	ADDQ AX, R12
+	ADCQ DX, R11
 
 	// r3 += 19×l4×l4
-	MOVQ   32(CX), AX
-	IMUL3Q $0x13, AX, AX
-	MULQ   32(CX)
-	ADDQ   AX, R12
-	ADCQ   DX, R11
+	MOVQ 32(CX), DX
+	LEAQ (DX)(DX*8), AX
+	LEAQ (DX)(AX*2), AX
+	MULQ 32(CX)
+	ADDQ AX, R12
+	ADCQ DX, R11
 
 	// r4 = 2×l0×l4
 	MOVQ (CX), AX
@@ -315,11 +334,11 @@ TEXT ·feSquare(SB), NOSPLIT, $0-16
 	MOVQ DX, R13
 
 	// r4 += 2×l1×l3
-	MOVQ   8(CX), AX
-	IMUL3Q $0x02, AX, AX
-	MULQ   24(CX)
-	ADDQ   AX, R14
-	ADCQ   DX, R13
+	MOVQ 8(CX), AX
+	SHLQ $0x01, AX
+	MULQ 24(CX)
+	ADDQ AX, R14
+	ADCQ DX, R13
 
 	// r4 += l2×l2
 	MOVQ 16(CX), AX

+ 1 - 2
vendor/filippo.io/edwards25519/field/fe_amd64_noasm.go

@@ -2,8 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build !amd64 || !gc || purego
-// +build !amd64 !gc purego
+//go:build !amd64 || purego
 
 package field
 

+ 0 - 16
vendor/filippo.io/edwards25519/field/fe_arm64.go

@@ -1,16 +0,0 @@
-// Copyright (c) 2020 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build arm64 && gc && !purego
-// +build arm64,gc,!purego
-
-package field
-
-//go:noescape
-func carryPropagate(v *Element)
-
-func (v *Element) carryPropagate() *Element {
-	carryPropagate(v)
-	return v
-}

+ 0 - 42
vendor/filippo.io/edwards25519/field/fe_arm64.s

@@ -1,42 +0,0 @@
-// Copyright (c) 2020 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build arm64 && gc && !purego
-
-#include "textflag.h"
-
-// carryPropagate works exactly like carryPropagateGeneric and uses the
-// same AND, ADD, and LSR+MADD instructions emitted by the compiler, but
-// avoids loading R0-R4 twice and uses LDP and STP.
-//
-// See https://golang.org/issues/43145 for the main compiler issue.
-//
-// func carryPropagate(v *Element)
-TEXT ·carryPropagate(SB),NOFRAME|NOSPLIT,$0-8
-	MOVD v+0(FP), R20
-
-	LDP 0(R20), (R0, R1)
-	LDP 16(R20), (R2, R3)
-	MOVD 32(R20), R4
-
-	AND $0x7ffffffffffff, R0, R10
-	AND $0x7ffffffffffff, R1, R11
-	AND $0x7ffffffffffff, R2, R12
-	AND $0x7ffffffffffff, R3, R13
-	AND $0x7ffffffffffff, R4, R14
-
-	ADD R0>>51, R11, R11
-	ADD R1>>51, R12, R12
-	ADD R2>>51, R13, R13
-	ADD R3>>51, R14, R14
-	// R4>>51 * 19 + R10 -> R10
-	LSR $51, R4, R21
-	MOVD $19, R22
-	MADD R22, R10, R21, R10
-
-	STP (R10, R11), 0(R20)
-	STP (R12, R13), 16(R20)
-	MOVD R14, 32(R20)
-
-	RET

+ 0 - 12
vendor/filippo.io/edwards25519/field/fe_arm64_noasm.go

@@ -1,12 +0,0 @@
-// Copyright (c) 2021 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build !arm64 || !gc || purego
-// +build !arm64 !gc purego
-
-package field
-
-func (v *Element) carryPropagate() *Element {
-	return v.carryPropagateGeneric()
-}

+ 88 - 82
vendor/filippo.io/edwards25519/field/fe_generic.go

@@ -12,20 +12,42 @@ type uint128 struct {
 	lo, hi uint64
 }
 
-// mul64 returns a * b.
-func mul64(a, b uint64) uint128 {
+// mul returns a * b.
+func mul(a, b uint64) uint128 {
 	hi, lo := bits.Mul64(a, b)
 	return uint128{lo, hi}
 }
 
-// addMul64 returns v + a * b.
-func addMul64(v uint128, a, b uint64) uint128 {
+// addMul returns v + a * b.
+func addMul(v uint128, a, b uint64) uint128 {
 	hi, lo := bits.Mul64(a, b)
 	lo, c := bits.Add64(lo, v.lo, 0)
 	hi, _ = bits.Add64(hi, v.hi, c)
 	return uint128{lo, hi}
 }
 
+// mul19 returns v * 19.
+func mul19(v uint64) uint64 {
+	// Using this approach seems to yield better optimizations than *19.
+	return v + (v+v<<3)<<1
+}
+
+// addMul19 returns v + 19 * a * b, where a and b are at most 52 bits.
+func addMul19(v uint128, a, b uint64) uint128 {
+	hi, lo := bits.Mul64(mul19(a), b)
+	lo, c := bits.Add64(lo, v.lo, 0)
+	hi, _ = bits.Add64(hi, v.hi, c)
+	return uint128{lo, hi}
+}
+
+// addMul38 returns v + 38 * a * b, where a and b are at most 52 bits.
+func addMul38(v uint128, a, b uint64) uint128 {
+	hi, lo := bits.Mul64(mul19(a), b*2)
+	lo, c := bits.Add64(lo, v.lo, 0)
+	hi, _ = bits.Add64(hi, v.hi, c)
+	return uint128{lo, hi}
+}
+
 // shiftRightBy51 returns a >> 51. a is assumed to be at most 115 bits.
 func shiftRightBy51(a uint128) uint64 {
 	return (a.hi << (64 - 51)) | (a.lo >> 51)
@@ -76,45 +98,40 @@ func feMulGeneric(v, a, b *Element) {
 	//
 	// Finally we add up the columns into wide, overlapping limbs.
 
-	a1_19 := a1 * 19
-	a2_19 := a2 * 19
-	a3_19 := a3 * 19
-	a4_19 := a4 * 19
-
 	// r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1)
-	r0 := mul64(a0, b0)
-	r0 = addMul64(r0, a1_19, b4)
-	r0 = addMul64(r0, a2_19, b3)
-	r0 = addMul64(r0, a3_19, b2)
-	r0 = addMul64(r0, a4_19, b1)
+	r0 := mul(a0, b0)
+	r0 = addMul19(r0, a1, b4)
+	r0 = addMul19(r0, a2, b3)
+	r0 = addMul19(r0, a3, b2)
+	r0 = addMul19(r0, a4, b1)
 
 	// r1 = a0×b1 + a1×b0 + 19×(a2×b4 + a3×b3 + a4×b2)
-	r1 := mul64(a0, b1)
-	r1 = addMul64(r1, a1, b0)
-	r1 = addMul64(r1, a2_19, b4)
-	r1 = addMul64(r1, a3_19, b3)
-	r1 = addMul64(r1, a4_19, b2)
+	r1 := mul(a0, b1)
+	r1 = addMul(r1, a1, b0)
+	r1 = addMul19(r1, a2, b4)
+	r1 = addMul19(r1, a3, b3)
+	r1 = addMul19(r1, a4, b2)
 
 	// r2 = a0×b2 + a1×b1 + a2×b0 + 19×(a3×b4 + a4×b3)
-	r2 := mul64(a0, b2)
-	r2 = addMul64(r2, a1, b1)
-	r2 = addMul64(r2, a2, b0)
-	r2 = addMul64(r2, a3_19, b4)
-	r2 = addMul64(r2, a4_19, b3)
+	r2 := mul(a0, b2)
+	r2 = addMul(r2, a1, b1)
+	r2 = addMul(r2, a2, b0)
+	r2 = addMul19(r2, a3, b4)
+	r2 = addMul19(r2, a4, b3)
 
 	// r3 = a0×b3 + a1×b2 + a2×b1 + a3×b0 + 19×a4×b4
-	r3 := mul64(a0, b3)
-	r3 = addMul64(r3, a1, b2)
-	r3 = addMul64(r3, a2, b1)
-	r3 = addMul64(r3, a3, b0)
-	r3 = addMul64(r3, a4_19, b4)
+	r3 := mul(a0, b3)
+	r3 = addMul(r3, a1, b2)
+	r3 = addMul(r3, a2, b1)
+	r3 = addMul(r3, a3, b0)
+	r3 = addMul19(r3, a4, b4)
 
 	// r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0
-	r4 := mul64(a0, b4)
-	r4 = addMul64(r4, a1, b3)
-	r4 = addMul64(r4, a2, b2)
-	r4 = addMul64(r4, a3, b1)
-	r4 = addMul64(r4, a4, b0)
+	r4 := mul(a0, b4)
+	r4 = addMul(r4, a1, b3)
+	r4 = addMul(r4, a2, b2)
+	r4 = addMul(r4, a3, b1)
+	r4 = addMul(r4, a4, b0)
 
 	// After the multiplication, we need to reduce (carry) the five coefficients
 	// to obtain a result with limbs that are at most slightly larger than 2⁵¹,
@@ -149,7 +166,7 @@ func feMulGeneric(v, a, b *Element) {
 	c3 := shiftRightBy51(r3)
 	c4 := shiftRightBy51(r4)
 
-	rr0 := r0.lo&maskLow51Bits + c4*19
+	rr0 := r0.lo&maskLow51Bits + mul19(c4)
 	rr1 := r1.lo&maskLow51Bits + c0
 	rr2 := r2.lo&maskLow51Bits + c1
 	rr3 := r3.lo&maskLow51Bits + c2
@@ -158,8 +175,12 @@ func feMulGeneric(v, a, b *Element) {
 	// Now all coefficients fit into 64-bit registers but are still too large to
 	// be passed around as an Element. We therefore do one last carry chain,
 	// where the carries will be small enough to fit in the wiggle room above 2⁵¹.
-	*v = Element{rr0, rr1, rr2, rr3, rr4}
-	v.carryPropagate()
+
+	v.l0 = rr0&maskLow51Bits + mul19(rr4>>51)
+	v.l1 = rr1&maskLow51Bits + rr0>>51
+	v.l2 = rr2&maskLow51Bits + rr1>>51
+	v.l3 = rr3&maskLow51Bits + rr2>>51
+	v.l4 = rr4&maskLow51Bits + rr3>>51
 }
 
 func feSquareGeneric(v, a *Element) {
@@ -190,44 +211,31 @@ func feSquareGeneric(v, a *Element) {
 	//            l0l4 19×l4l4 19×l3l4 19×l2l4 19×l1l4  =
 	//           --------------------------------------
 	//              r4      r3      r2      r1      r0
-	//
-	// With precomputed 2×, 19×, and 2×19× terms, we can compute each limb with
-	// only three Mul64 and four Add64, instead of five and eight.
-
-	l0_2 := l0 * 2
-	l1_2 := l1 * 2
-
-	l1_38 := l1 * 38
-	l2_38 := l2 * 38
-	l3_38 := l3 * 38
-
-	l3_19 := l3 * 19
-	l4_19 := l4 * 19
 
 	// r0 = l0×l0 + 19×(l1×l4 + l2×l3 + l3×l2 + l4×l1) = l0×l0 + 19×2×(l1×l4 + l2×l3)
-	r0 := mul64(l0, l0)
-	r0 = addMul64(r0, l1_38, l4)
-	r0 = addMul64(r0, l2_38, l3)
+	r0 := mul(l0, l0)
+	r0 = addMul38(r0, l1, l4)
+	r0 = addMul38(r0, l2, l3)
 
 	// r1 = l0×l1 + l1×l0 + 19×(l2×l4 + l3×l3 + l4×l2) = 2×l0×l1 + 19×2×l2×l4 + 19×l3×l3
-	r1 := mul64(l0_2, l1)
-	r1 = addMul64(r1, l2_38, l4)
-	r1 = addMul64(r1, l3_19, l3)
+	r1 := mul(l0*2, l1)
+	r1 = addMul38(r1, l2, l4)
+	r1 = addMul19(r1, l3, l3)
 
 	// r2 = l0×l2 + l1×l1 + l2×l0 + 19×(l3×l4 + l4×l3) = 2×l0×l2 + l1×l1 + 19×2×l3×l4
-	r2 := mul64(l0_2, l2)
-	r2 = addMul64(r2, l1, l1)
-	r2 = addMul64(r2, l3_38, l4)
+	r2 := mul(l0*2, l2)
+	r2 = addMul(r2, l1, l1)
+	r2 = addMul38(r2, l3, l4)
 
 	// r3 = l0×l3 + l1×l2 + l2×l1 + l3×l0 + 19×l4×l4 = 2×l0×l3 + 2×l1×l2 + 19×l4×l4
-	r3 := mul64(l0_2, l3)
-	r3 = addMul64(r3, l1_2, l2)
-	r3 = addMul64(r3, l4_19, l4)
+	r3 := mul(l0*2, l3)
+	r3 = addMul(r3, l1*2, l2)
+	r3 = addMul19(r3, l4, l4)
 
 	// r4 = l0×l4 + l1×l3 + l2×l2 + l3×l1 + l4×l0 = 2×l0×l4 + 2×l1×l3 + l2×l2
-	r4 := mul64(l0_2, l4)
-	r4 = addMul64(r4, l1_2, l3)
-	r4 = addMul64(r4, l2, l2)
+	r4 := mul(l0*2, l4)
+	r4 = addMul(r4, l1*2, l3)
+	r4 = addMul(r4, l2, l2)
 
 	c0 := shiftRightBy51(r0)
 	c1 := shiftRightBy51(r1)
@@ -235,32 +243,30 @@ func feSquareGeneric(v, a *Element) {
 	c3 := shiftRightBy51(r3)
 	c4 := shiftRightBy51(r4)
 
-	rr0 := r0.lo&maskLow51Bits + c4*19
+	rr0 := r0.lo&maskLow51Bits + mul19(c4)
 	rr1 := r1.lo&maskLow51Bits + c0
 	rr2 := r2.lo&maskLow51Bits + c1
 	rr3 := r3.lo&maskLow51Bits + c2
 	rr4 := r4.lo&maskLow51Bits + c3
 
-	*v = Element{rr0, rr1, rr2, rr3, rr4}
-	v.carryPropagate()
+	v.l0 = rr0&maskLow51Bits + mul19(rr4>>51)
+	v.l1 = rr1&maskLow51Bits + rr0>>51
+	v.l2 = rr2&maskLow51Bits + rr1>>51
+	v.l3 = rr3&maskLow51Bits + rr2>>51
+	v.l4 = rr4&maskLow51Bits + rr3>>51
 }
 
-// carryPropagateGeneric brings the limbs below 52 bits by applying the reduction
+// carryPropagate brings the limbs below 52 bits by applying the reduction
 // identity (a * 2²⁵⁵ + b = a * 19 + b) to the l4 carry.
-func (v *Element) carryPropagateGeneric() *Element {
-	c0 := v.l0 >> 51
-	c1 := v.l1 >> 51
-	c2 := v.l2 >> 51
-	c3 := v.l3 >> 51
-	c4 := v.l4 >> 51
-
-	// c4 is at most 64 - 51 = 13 bits, so c4*19 is at most 18 bits, and
+func (v *Element) carryPropagate() *Element {
+	// (l4>>51) is at most 64 - 51 = 13 bits, so (l4>>51)*19 is at most 18 bits, and
 	// the final l0 will be at most 52 bits. Similarly for the rest.
-	v.l0 = v.l0&maskLow51Bits + c4*19
-	v.l1 = v.l1&maskLow51Bits + c0
-	v.l2 = v.l2&maskLow51Bits + c1
-	v.l3 = v.l3&maskLow51Bits + c2
-	v.l4 = v.l4&maskLow51Bits + c3
+	l0 := v.l0
+	v.l0 = v.l0&maskLow51Bits + mul19(v.l4>>51)
+	v.l4 = v.l4&maskLow51Bits + v.l3>>51
+	v.l3 = v.l3&maskLow51Bits + v.l2>>51
+	v.l2 = v.l2&maskLow51Bits + v.l1>>51
+	v.l1 = v.l1&maskLow51Bits + l0>>51
 
 	return v
 }

+ 53 - 0
vendor/filippo.io/edwards25519/pull.sh

@@ -0,0 +1,53 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+if [ "$#" -ne 1 ]; then
+	echo "Usage: $0 <tag>"
+	exit 1
+fi
+
+TAG="$1"
+TMPDIR="$(mktemp -d)"
+
+cleanup() {
+	rm -rf "$TMPDIR"
+}
+trap cleanup EXIT
+
+command -v git >/dev/null
+command -v git-filter-repo >/dev/null
+
+if [ -d "$HOME/go/.git" ]; then
+	REFERENCE=(--reference "$HOME/go" --dissociate)
+else
+	REFERENCE=()
+fi
+
+git -c advice.detachedHead=false clone --no-checkout "${REFERENCE[@]}" \
+	-b "$TAG" https://go.googlesource.com/go.git "$TMPDIR"
+
+# Simplify the history graph by removing the dev.boringcrypto branches, whose
+# merges end up empty after grafting anyway. This also fixes a weird quirk
+# (maybe a git-filter-repo bug?) where only one file from an old path,
+# src/crypto/ed25519/internal/edwards25519/const.go, would still exist in the
+# filtered repo.
+git -C "$TMPDIR" replace --graft f771edd7f9 99f1bf54eb
+git -C "$TMPDIR" replace --graft 109c13b64f c2f96e686f
+git -C "$TMPDIR" replace --graft aa4da4f189 912f075047
+
+git -C "$TMPDIR" filter-repo --force \
+	--paths-from-file /dev/stdin \
+	--prune-empty always \
+	--prune-degenerate always \
+	--tag-callback 'tag.skip()' <<'EOF'
+src/crypto/internal/fips140/edwards25519
+src/crypto/internal/edwards25519
+src/crypto/ed25519/internal/edwards25519
+EOF
+
+git fetch "$TMPDIR"
+git update-ref "refs/heads/upstream/$TAG" FETCH_HEAD
+
+echo
+echo "Fetched upstream history up to $TAG. Merge with:"
+echo -e "\tgit merge --no-ff --no-commit --allow-unrelated-histories upstream/$TAG"

+ 18 - 9
vendor/filippo.io/edwards25519/scalar.go

@@ -7,6 +7,7 @@ package edwards25519
 import (
 	"encoding/binary"
 	"errors"
+	"math/bits"
 )
 
 // A Scalar is an integer modulo
@@ -179,15 +180,23 @@ func isReduced(s []byte) bool {
 		return false
 	}
 
-	for i := len(s) - 1; i >= 0; i-- {
-		switch {
-		case s[i] > scalarMinusOneBytes[i]:
-			return false
-		case s[i] < scalarMinusOneBytes[i]:
-			return true
-		}
-	}
-	return true
+	s0 := binary.LittleEndian.Uint64(s[:8])
+	s1 := binary.LittleEndian.Uint64(s[8:16])
+	s2 := binary.LittleEndian.Uint64(s[16:24])
+	s3 := binary.LittleEndian.Uint64(s[24:])
+
+	l0 := binary.LittleEndian.Uint64(scalarMinusOneBytes[:8])
+	l1 := binary.LittleEndian.Uint64(scalarMinusOneBytes[8:16])
+	l2 := binary.LittleEndian.Uint64(scalarMinusOneBytes[16:24])
+	l3 := binary.LittleEndian.Uint64(scalarMinusOneBytes[24:])
+
+	// Do a constant time subtraction chain scalarMinusOneBytes - s. If there is
+	// a borrow at the end, then s > scalarMinusOneBytes.
+	_, b := bits.Sub64(l0, s0, 0)
+	_, b = bits.Sub64(l1, s1, b)
+	_, b = bits.Sub64(l2, s2, b)
+	_, b = bits.Sub64(l3, s3, b)
+	return b == 0
 }
 
 // SetBytesWithClamping applies the buffer pruning described in RFC 8032,

+ 1 - 3
vendor/filippo.io/edwards25519/tables.go

@@ -4,9 +4,7 @@
 
 package edwards25519
 
-import (
-	"crypto/subtle"
-)
+import "crypto/subtle"
 
 // A dynamic lookup table for variable-base, constant-time scalar muls.
 type projLookupTable struct {

+ 2 - 2
vendor/modules.txt

@@ -1,8 +1,8 @@
 # filippo.io/bigmod v0.0.1
 ## explicit; go 1.20
 filippo.io/bigmod
-# filippo.io/edwards25519 v1.1.0
-## explicit; go 1.20
+# filippo.io/edwards25519 v1.2.0
+## explicit; go 1.24.0
 filippo.io/edwards25519
 filippo.io/edwards25519/field
 # filippo.io/keygen v0.0.0-20230306160926-5201437acf8e