Преглед изворни кода

Merge branch 'master' into staging-client

Rod Hynes пре 2 недеља
родитељ
комит
affa0dfab7
38 измењених фајлова са 574 додато и 321 уклоњено
  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
      psiphon/server/pb/psiphond/psiphond.desc
  18. BIN
      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
 replace github.com/pion/webrtc/v3 => ./replace/webrtc
 
 
 require (
 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-sdk v0.0.16
 	github.com/Jigsaw-Code/outline-ss-server v1.8.0
 	github.com/Jigsaw-Code/outline-ss-server v1.8.0
 	github.com/Psiphon-Inc/rotate-safe-writer v0.0.0-20210303140923-464a7a37606e
 	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/bigmod v0.0.1/go.mod h1:KyzqAbH7bRH6MOuOF1TPfUjvLoi0mRF2bIyD2ouRNQI=
 filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
 filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
 filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
 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 h1:+xwUCyMiCWKWsI0RowhzB4sngpUdMHgU6lLuWJCX5Dg=
 filippo.io/keygen v0.0.0-20230306160926-5201437acf8e/go.mod h1:ZGSiF/b2hd6MRghF/cid0vXw8pXykRTmIu+JSPw/NCQ=
 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=
 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_common_compartment_ids"] = hasCommonCompartmentIDs
 	logFields["has_personal_compartment_ids"] = hasPersonalCompartmentIDs
 	logFields["has_personal_compartment_ids"] = hasPersonalCompartmentIDs
 	logFields["ice_candidate_types"] = request.ICECandidateTypes
 	logFields["ice_candidate_types"] = request.ICECandidateTypes
+	logFields["has_IPv4"] = sdpMetrics.hasIPv4
 	logFields["has_IPv6"] = sdpMetrics.hasIPv6
 	logFields["has_IPv6"] = sdpMetrics.hasIPv6
 	logFields["has_private_IP"] = sdpMetrics.hasPrivateIP
 	logFields["has_private_IP"] = sdpMetrics.hasPrivateIP
 	logFields["filtered_ice_candidates"] = sdpMetrics.filteredICECandidates
 	logFields["filtered_ice_candidates"] = sdpMetrics.filteredICECandidates
@@ -960,6 +961,7 @@ func (request *ProxyAnswerRequest) ValidateAndGetLogFields(
 	logFields["ice_candidate_types"] = request.ICECandidateTypes
 	logFields["ice_candidate_types"] = request.ICECandidateTypes
 	logFields["answer_error"] = request.AnswerError
 	logFields["answer_error"] = request.AnswerError
 	if sdpMetrics != nil {
 	if sdpMetrics != nil {
+		logFields["has_IPv4"] = sdpMetrics.hasIPv4
 		logFields["has_IPv6"] = sdpMetrics.hasIPv6
 		logFields["has_IPv6"] = sdpMetrics.hasIPv6
 		logFields["has_private_IP"] = sdpMetrics.hasPrivateIP
 		logFields["has_private_IP"] = sdpMetrics.hasPrivateIP
 		logFields["filtered_ice_candidates"] = sdpMetrics.filteredICECandidates
 		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"] =
 			logFields["preferred_nat_match"] =
 				clientMatchOffer.Properties.IsPreferredNATMatch(&proxyMatchAnnouncement.Properties)
 				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.
 			// client, these values are added by ValidateAndGetLogFields.
 		}
 		}
 		if timedOut {
 		if timedOut {

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

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

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

@@ -228,6 +228,7 @@ type MatchMetrics struct {
 	AnnouncementMatchIndex int
 	AnnouncementMatchIndex int
 	AnnouncementQueueSize  int
 	AnnouncementQueueSize  int
 	PendingAnswersSize     int
 	PendingAnswersSize     int
+	MatchDuration          time.Duration
 }
 }
 
 
 // GetMetrics converts MatchMetrics to loggable fields.
 // GetMetrics converts MatchMetrics to loggable fields.
@@ -235,6 +236,11 @@ func (metrics *MatchMetrics) GetMetrics() common.LogFields {
 	if metrics == nil {
 	if metrics == nil {
 		return 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{
 	return common.LogFields{
 		"offer_deadline":           int64(metrics.OfferDeadline / time.Millisecond),
 		"offer_deadline":           int64(metrics.OfferDeadline / time.Millisecond),
 		"offer_match_index":        metrics.OfferMatchIndex,
 		"offer_match_index":        metrics.OfferMatchIndex,
@@ -242,6 +248,7 @@ func (metrics *MatchMetrics) GetMetrics() common.LogFields {
 		"announcement_match_index": metrics.AnnouncementMatchIndex,
 		"announcement_match_index": metrics.AnnouncementMatchIndex,
 		"announcement_queue_size":  metrics.AnnouncementQueueSize,
 		"announcement_queue_size":  metrics.AnnouncementQueueSize,
 		"pending_answers_size":     metrics.PendingAnswersSize,
 		"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.
 // matchAllOffers iterates over the queues, making all possible matches.
 func (m *Matcher) matchAllOffers() {
 func (m *Matcher) matchAllOffers() {
 
 
+	// Include lock acquisition time in MatchDuration metric.
+	startTime := time.Now()
+
 	m.announcementQueueMutex.Lock()
 	m.announcementQueueMutex.Lock()
 	defer m.announcementQueueMutex.Unlock()
 	defer m.announcementQueueMutex.Unlock()
 	m.offerQueueMutex.Lock()
 	m.offerQueueMutex.Lock()
@@ -732,6 +742,12 @@ func (m *Matcher) matchAllOffers() {
 		// The index metrics predate the announcement multi-queue; now, with
 		// The index metrics predate the announcement multi-queue; now, with
 		// the multi-queue, announcement_index is how many announce entries
 		// the multi-queue, announcement_index is how many announce entries
 		// were inspected before matching.
 		// 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{
 		matchMetrics := &MatchMetrics{
 			OfferDeadline:          untilOfferDeadline,
 			OfferDeadline:          untilOfferDeadline,
@@ -740,6 +756,7 @@ func (m *Matcher) matchAllOffers() {
 			AnnouncementMatchIndex: announcementMatchIndex,
 			AnnouncementMatchIndex: announcementMatchIndex,
 			AnnouncementQueueSize:  m.announcementQueue.getLen(),
 			AnnouncementQueueSize:  m.announcementQueue.getLen(),
 			PendingAnswersSize:     m.pendingAnswers.ItemCount(),
 			PendingAnswersSize:     m.pendingAnswers.ItemCount(),
+			MatchDuration:          time.Since(startTime),
 		}
 		}
 
 
 		offerEntry.matchMetrics.Store(matchMetrics)
 		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
 		// Check if this is a preferred NAT match. Ultimately, a match may be
 		// made with potentially incompatible NATs, but the client/proxy
 		// made with potentially incompatible NATs, but the client/proxy
 		// reported NAT types may be incorrect or unknown; the client will
 		// reported NAT types may be incorrect or unknown; the client will
@@ -1301,6 +1324,7 @@ type announcementMultiQueue struct {
 	commonCompartmentQueues         map[ID]*announcementCompartmentQueue
 	commonCompartmentQueues         map[ID]*announcementCompartmentQueue
 	personalCompartmentQueues       map[ID]*announcementCompartmentQueue
 	personalCompartmentQueues       map[ID]*announcementCompartmentQueue
 	totalEntries                    int
 	totalEntries                    int
+	iterator                        *announcementMatchIterator
 }
 }
 
 
 // announcementCompartmentQueue is a single compartment queue within an
 // announcementCompartmentQueue is a single compartment queue within an
@@ -1325,7 +1349,6 @@ type announcementCompartmentQueue struct {
 type announcementMatchIterator struct {
 type announcementMatchIterator struct {
 	multiQueue        *announcementMultiQueue
 	multiQueue        *announcementMultiQueue
 	compartmentQueues []*announcementCompartmentQueue
 	compartmentQueues []*announcementCompartmentQueue
-	compartmentIDs    []ID
 	nextEntries       []*list.Element
 	nextEntries       []*list.Element
 }
 }
 
 
@@ -1338,11 +1361,15 @@ type announcementQueueReference struct {
 }
 }
 
 
 func newAnnouncementMultiQueue() *announcementMultiQueue {
 func newAnnouncementMultiQueue() *announcementMultiQueue {
-	return &announcementMultiQueue{
+	q := &announcementMultiQueue{
 		priorityCommonCompartmentQueues: make(map[ID]*announcementCompartmentQueue),
 		priorityCommonCompartmentQueues: make(map[ID]*announcementCompartmentQueue),
 		commonCompartmentQueues:         make(map[ID]*announcementCompartmentQueue),
 		commonCompartmentQueues:         make(map[ID]*announcementCompartmentQueue),
 		personalCompartmentQueues:       make(map[ID]*announcementCompartmentQueue),
 		personalCompartmentQueues:       make(map[ID]*announcementCompartmentQueue),
 	}
 	}
+	q.iterator = &announcementMatchIterator{
+		multiQueue: q,
+	}
+	return q
 }
 }
 
 
 func (q *announcementMultiQueue) getLen() int {
 func (q *announcementMultiQueue) getLen() int {
@@ -1471,13 +1498,27 @@ func (r *announcementQueueReference) dequeue() bool {
 	return true
 	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(
 func (q *announcementMultiQueue) startMatching(
 	isCommonCompartments bool,
 	isCommonCompartments bool,
 	compartmentIDs []ID) *announcementMatchIterator {
 	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
 	// Find the matching compartment queues and initialize iteration over
 	// those queues. Building the set of matching queues is a linear time
 	// those queues. Building the set of matching queues is a linear time
@@ -1505,7 +1546,6 @@ func (q *announcementMultiQueue) startMatching(
 		for _, ID := range compartmentIDs {
 		for _, ID := range compartmentIDs {
 			if compartmentQueue, ok := compartmentQueues[ID]; ok {
 			if compartmentQueue, ok := compartmentQueues[ID]; ok {
 				iter.compartmentQueues = append(iter.compartmentQueues, compartmentQueue)
 				iter.compartmentQueues = append(iter.compartmentQueues, compartmentQueue)
-				iter.compartmentIDs = append(iter.compartmentIDs, ID)
 				iter.nextEntries = append(iter.nextEntries, compartmentQueue.entries.Front())
 				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
 	// A potential future enhancement is to add more iterator state to track
 	// which queue has the next oldest time to select on the following
 	// which queue has the next oldest time to select on the following
 	// getNext call. Another potential enhancement is to remove fully
 	// getNext call. Another potential enhancement is to remove fully
-	// consumed queues from compartmentQueues/compartmentIDs/nextEntries.
+	// consumed queues from compartmentQueues/nextEntries.
 
 
 	var selectedCandidate *announcementEntry
 	var selectedCandidate *announcementEntry
 	selectedIndex := -1
 	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.
 	// and the provider can select new parameters per connection as reqired.
 	MakeWebRTCDialCoordinator func() (WebRTCDialCoordinator, error)
 	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,
 	// HandleTacticsPayload is a callback that receives any tactics payload,
 	// provided by the broker in proxy announcement request responses.
 	// provided by the broker in proxy announcement request responses.
 	// HandleTacticsPayload must return true when the tacticsPayload includes
 	// HandleTacticsPayload must return true when the tacticsPayload includes
@@ -1148,6 +1152,7 @@ func (p *Proxy) proxyOneClient(
 		&webRTCConfig{
 		&webRTCConfig{
 			Logger:                      p.config.Logger,
 			Logger:                      p.config.Logger,
 			EnableDebugLogging:          p.config.EnableWebRTCDebugLogging,
 			EnableDebugLogging:          p.config.EnableWebRTCDebugLogging,
+			ExcludeInterfaceName:        p.config.ExcludeInterfaceName,
 			WebRTCDialCoordinator:       webRTCCoordinator,
 			WebRTCDialCoordinator:       webRTCCoordinator,
 			ClientRootObfuscationSecret: announceResponse.ClientRootObfuscationSecret,
 			ClientRootObfuscationSecret: announceResponse.ClientRootObfuscationSecret,
 			DoDTLSRandomization:         announceResponse.DoDTLSRandomization,
 			DoDTLSRandomization:         announceResponse.DoDTLSRandomization,

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

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

+ 24 - 0
psiphon/config.go

@@ -31,6 +31,7 @@ import (
 	"path/filepath"
 	"path/filepath"
 	"reflect"
 	"reflect"
 	"regexp"
 	"regexp"
+	"runtime"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 	"sync"
 	"sync"
@@ -658,6 +659,16 @@ type Config struct {
 	// ephemeral key will be generated.
 	// ephemeral key will be generated.
 	InproxyProxySessionPrivateKey string `json:",omitempty"`
 	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
 	// InproxyMaxClients specifies the maximum number of common in-proxy
 	// clients to be proxied concurrently. When InproxyEnableProxy is set,
 	// clients to be proxied concurrently. When InproxyEnableProxy is set,
 	// it can only be 0 when InProxyMaxPersonalClients is > 0.
 	// it can only be 0 when InProxyMaxPersonalClients is > 0.
@@ -1196,6 +1207,8 @@ type Config struct {
 	ServerEntryIteratorMaxMoveToFront   *int     `json:",omitempty"`
 	ServerEntryIteratorMaxMoveToFront   *int     `json:",omitempty"`
 	ServerEntryIteratorResetProbability *float64 `json:",omitempty"`
 	ServerEntryIteratorResetProbability *float64 `json:",omitempty"`
 
 
+	TunnelConnectTimeoutSeconds *int `json:",omitempty"`
+
 	// params is the active parameters.Parameters with defaults, config values,
 	// params is the active parameters.Parameters with defaults, config values,
 	// and, optionally, tactics applied.
 	// and, optionally, tactics applied.
 	//
 	//
@@ -1572,6 +1585,13 @@ func (config *Config) Commit(migrateFromLegacyFields bool) error {
 		return errors.TraceNew("invalid ObfuscatedSSHAlgorithms")
 		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.InproxyEnableProxy {
 
 
 		if config.InproxyMaxCommonClients+config.InproxyMaxPersonalClients <= 0 {
 		if config.InproxyMaxCommonClients+config.InproxyMaxPersonalClients <= 0 {
@@ -3085,6 +3105,10 @@ func (config *Config) makeConfigParameters() map[string]interface{} {
 		applyParameters[parameters.EnableDSLFetcher] = *config.EnableDSLFetcher
 		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
 	// When adding new config dial parameters that may override tactics, also
 	// update setDialParametersHash.
 	// update setDialParametersHash.
 
 

+ 1 - 0
psiphon/controller.go

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

+ 6 - 4
psiphon/dataStoreRecovery_test.go

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

+ 1 - 1
psiphon/dataStore_bolt.go

@@ -222,7 +222,7 @@ func (db *datastoreDB) isDatastoreFailed() bool {
 
 
 func (db *datastoreDB) setDatastoreFailed(r interface{}) {
 func (db *datastoreDB) setDatastoreFailed(r interface{}) {
 	atomic.StoreInt32(&db.isFailed, 1)
 	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 {
 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/parameters"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng"
 	"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/resolver"
+	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/tun"
 	utls "github.com/Psiphon-Labs/utls"
 	utls "github.com/Psiphon-Labs/utls"
 	"github.com/cespare/xxhash"
 	"github.com/cespare/xxhash"
 )
 )
@@ -2003,6 +2004,8 @@ func (w *InproxyWebRTCDialInstance) ProxyUpstreamDial(
 	// DNSResolverPreresolvedIPAddressCIDRs proxy tactics. In addition,
 	// DNSResolverPreresolvedIPAddressCIDRs proxy tactics. In addition,
 	// replay the selected upstream dial tactics parameters.
 	// replay the selected upstream dial tactics parameters.
 
 
+	splitUpstreamInterfaceName := w.config.InproxyProxySplitUpstreamInterfaceName
+
 	dialer := net.Dialer{
 	dialer := net.Dialer{
 		Control: func(_, _ string, c syscall.RawConn) error {
 		Control: func(_, _ string, c syscall.RawConn) error {
 			var controlErr error
 			var controlErr error
@@ -2018,6 +2021,12 @@ func (w *InproxyWebRTCDialInstance) ProxyUpstreamDial(
 						controlErr = errors.Tracef("BindToDevice failed: %s", err)
 						controlErr = errors.Tracef("BindToDevice failed: %s", err)
 						return
 						return
 					}
 					}
+				} else if splitUpstreamInterfaceName != "" {
+					err := tun.BindToDevice(socketFD, splitUpstreamInterfaceName)
+					if err != nil {
+						controlErr = errors.Tracef("BindToDevice failed: %s", err)
+						return
+					}
 				}
 				}
 			})
 			})
 			if controlErr != nil {
 			if controlErr != nil {

+ 1 - 1
psiphon/notice.go

@@ -1465,7 +1465,7 @@ func (log *commonLogTrace) Info(args ...interface{}) {
 }
 }
 
 
 func (log *commonLogTrace) Warning(args ...interface{}) {
 func (log *commonLogTrace) Warning(args ...interface{}) {
-	log.outputNotice("Alert", args...)
+	log.outputNotice("Warning", args...)
 }
 }
 
 
 func (log *commonLogTrace) Error(args ...interface{}) {
 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"`
 	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"`
 	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"`
 	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
 	unknownFields                 protoimpl.UnknownFields
 	sizeCache                     protoimpl.SizeCache
 	sizeCache                     protoimpl.SizeCache
 }
 }
@@ -385,11 +389,39 @@ func (x *InproxyBroker) GetOfferDeadline() int64 {
 	return 0
 	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
 var File_ca_psiphon_psiphond_inproxy_broker_proto protoreflect.FileDescriptor
 
 
 const file_ca_psiphon_psiphond_inproxy_broker_proto_rawDesc = "" +
 const file_ca_psiphon_psiphond_inproxy_broker_proto_rawDesc = "" +
 	"\n" +
 	"\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" +
 	"\rInproxyBroker\x12E\n" +
 	"\vbase_params\x18\x01 \x01(\v2\x1f.ca.psiphon.psiphond.BaseParamsH\x00R\n" +
 	"\vbase_params\x18\x01 \x01(\v2\x1f.ca.psiphon.psiphond.BaseParamsH\x00R\n" +
 	"baseParams\x88\x01\x01\x12=\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" +
 	"\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" +
 	"\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" +
 	"\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" +
 	"\f_base_paramsB\x1b\n" +
 	"\x19_announcement_match_indexB\x1a\n" +
 	"\x19_announcement_match_indexB\x1a\n" +
 	"\x18_announcement_queue_sizeB\x0f\n" +
 	"\x18_announcement_queue_sizeB\x0f\n" +
@@ -475,7 +511,11 @@ const file_ca_psiphon_psiphond_inproxy_broker_proto_rawDesc = "" +
 	"_timed_outB\x1b\n" +
 	"_timed_outB\x1b\n" +
 	"\x19_meek_server_http_versionB\x17\n" +
 	"\x19_meek_server_http_versionB\x17\n" +
 	"\x15_pending_answers_sizeB\x11\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 (
 var (
 	file_ca_psiphon_psiphond_inproxy_broker_proto_rawDescOnce sync.Once
 	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"`
 	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"`
 	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"`
 	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
 	unknownFields                                    protoimpl.UnknownFields
 	sizeCache                                        protoimpl.SizeCache
 	sizeCache                                        protoimpl.SizeCache
 }
 }
@@ -769,11 +771,25 @@ func (x *InproxyDialParams) GetInproxyWebrtcUseMediaStreams() bool {
 	return false
 	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
 var File_ca_psiphon_psiphond_inproxy_dial_params_proto protoreflect.FileDescriptor
 
 
 const file_ca_psiphon_psiphond_inproxy_dial_params_proto_rawDesc = "" +
 const file_ca_psiphon_psiphond_inproxy_dial_params_proto_rawDesc = "" +
 	"\n" +
 	"\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" +
 	"\x11InproxyDialParams\x12>\n" +
 	"\x19inproxy_broker_client_bpf\x18\x01 \x01(\tH\x00R\x16inproxyBrokerClientBpf\x88\x01\x01\x12B\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" +
 	"\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" +
 	"\"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" +
 	"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_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" +
 	"\x1a_inproxy_broker_client_bpfB\x1e\n" +
 	"\x1c_inproxy_broker_dial_addressB\x1d\n" +
 	"\x1c_inproxy_broker_dial_addressB\x1d\n" +
 	"\x1b_inproxy_broker_dns_attemptB\x1f\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" +
 	"#_inproxy_webrtc_stun_server_RFC5780B9\n" +
 	"7_inproxy_webrtc_stun_server_RFC5780_resolved_ip_addressB1\n" +
 	"7_inproxy_webrtc_stun_server_RFC5780_resolved_ip_addressB1\n" +
 	"/_inproxy_webrtc_stun_server_resolved_ip_addressB#\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 (
 var (
 	file_ca_psiphon_psiphond_inproxy_dial_params_proto_rawDescOnce sync.Once
 	file_ca_psiphon_psiphond_inproxy_dial_params_proto_rawDescOnce sync.Once

BIN
psiphon/server/pb/psiphond/psiphond.desc


BIN
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 string meek_server_http_version = 137;
     optional int64 pending_answers_size = 138;
     optional int64 pending_answers_size = 138;
     optional int64 offer_deadline = 139;
     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_RFC5780_resolved_ip_address = 87;
     optional string inproxy_webrtc_stun_server_resolved_ip_address = 88;
     optional string inproxy_webrtc_stun_server_resolved_ip_address = 88;
     optional bool inproxy_webrtc_use_media_streams = 89;
     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##*.}"
   pkg="${src##*.}"
 
 
   mkdir -p "../pb/${pkg}" || fatal "failed to create compiled protobuf directory: ../pb/${pkg}"
   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
 done

+ 0 - 1
psiphon/server/protobufConverter.go

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

+ 2 - 0
psiphon/server/server_test.go

@@ -3277,6 +3277,8 @@ func checkExpectedServerTunnelLogFields(
 			"inproxy_proxy_network_type",
 			"inproxy_proxy_network_type",
 			"inproxy_proxy_protocol_version",
 			"inproxy_proxy_protocol_version",
 			"inproxy_proxy_nat_type",
 			"inproxy_proxy_nat_type",
+			"inproxy_proxy_max_common_clients",
+			"inproxy_proxy_max_personal_clients",
 			"inproxy_proxy_max_clients",
 			"inproxy_proxy_max_clients",
 			"inproxy_proxy_connecting_clients",
 			"inproxy_proxy_connecting_clients",
 			"inproxy_proxy_connected_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.
 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).
 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.
 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.
 // the curve used by the Ed25519 signature scheme.
 //
 //
 // Most users don't need this package, and should instead use crypto/ed25519 for
 // 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
 // However, developers who do need to interact with low-level edwards25519
 // operations can use this package, which is an extended version of
 // 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.
 // an importable module.
 package edwards25519
 package edwards25519

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

@@ -9,6 +9,7 @@ package edwards25519
 
 
 import (
 import (
 	"errors"
 	"errors"
+	"slices"
 
 
 	"filippo.io/edwards25519/field"
 	"filippo.io/edwards25519/field"
 )
 )
@@ -100,13 +101,15 @@ func (v *Point) bytesMontgomery(buf *[32]byte) []byte {
 	//
 	//
 	//              u = (1 + y) / (1 - y)
 	//              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)
 	return copyFieldElement(buf, &u)
 }
 }
@@ -124,7 +127,7 @@ func (v *Point) MultByCofactor(p *Point) *Point {
 	return v.fromP1xP1(&result)
 	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) {
 func (s *Scalar) pow2k(k int) {
 	for i := 0; i < k; i++ {
 	for i := 0; i < k; i++ {
 		s.Multiply(s, s)
 		s.Multiply(s, s)
@@ -250,12 +253,14 @@ func (v *Point) MultiScalarMult(scalars []*Scalar, points []*Point) *Point {
 	// between each point in the multiscalar equation.
 	// between each point in the multiscalar equation.
 
 
 	// Build lookup tables for each point
 	// 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 {
 	for i := range tables {
 		tables[i].FromP3(points[i])
 		tables[i].FromP3(points[i])
 	}
 	}
 	// Compute signed radix-16 digits for each scalar
 	// 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 {
 	for i := range digits {
 		digits[i] = scalars[i].signedRadix16()
 		digits[i] = scalars[i].signedRadix16()
 	}
 	}
@@ -265,6 +270,7 @@ func (v *Point) MultiScalarMult(scalars []*Scalar, points []*Point) *Point {
 	tmp1 := &projP1xP1{}
 	tmp1 := &projP1xP1{}
 	tmp2 := &projP2{}
 	tmp2 := &projP2{}
 	// Lookup-and-add the appropriate multiple of each input point
 	// Lookup-and-add the appropriate multiple of each input point
+	v.Set(NewIdentityPoint())
 	for j := range tables {
 	for j := range tables {
 		tables[j].SelectInto(multiple, digits[j][63])
 		tables[j].SelectInto(multiple, digits[j][63])
 		tmp1.Add(v, multiple) // tmp1 = v + x_(j,63)*Q in P1xP1 coords
 		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)
 	v.fromP2(tmp2)
 	return v
 	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.l2 = a.l2 + b.l2
 	v.l3 = a.l3 + b.l3
 	v.l3 = a.l3 + b.l3
 	v.l4 = a.l4 + b.l4
 	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.
 // Subtract sets v = a - b, and returns v.
@@ -232,18 +228,22 @@ func (v *Element) bytes(out *[32]byte) []byte {
 	t := *v
 	t := *v
 	t.reduce()
 	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[:]
 	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.
 // 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
 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.
 // 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"
 #include "textflag.h"
 
 
@@ -17,32 +16,36 @@ TEXT ·feMul(SB), NOSPLIT, $0-24
 	MOVQ DX, SI
 	MOVQ DX, SI
 
 
 	// r0 += 19×a1×b4
 	// 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
 	// 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
 	// 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
 	// 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
 	// r1 = a0×b1
 	MOVQ (CX), AX
 	MOVQ (CX), AX
@@ -57,25 +60,28 @@ TEXT ·feMul(SB), NOSPLIT, $0-24
 	ADCQ DX, R8
 	ADCQ DX, R8
 
 
 	// r1 += 19×a2×b4
 	// 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
 	// 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
 	// 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
 	// r2 = a0×b2
 	MOVQ (CX), AX
 	MOVQ (CX), AX
@@ -96,18 +102,20 @@ TEXT ·feMul(SB), NOSPLIT, $0-24
 	ADCQ DX, R10
 	ADCQ DX, R10
 
 
 	// r2 += 19×a3×b4
 	// 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
 	// 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
 	// r3 = a0×b3
 	MOVQ (CX), AX
 	MOVQ (CX), AX
@@ -134,11 +142,12 @@ TEXT ·feMul(SB), NOSPLIT, $0-24
 	ADCQ DX, R12
 	ADCQ DX, R12
 
 
 	// r3 += 19×a4×b4
 	// 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
 	// r4 = a0×b4
 	MOVQ (CX), AX
 	MOVQ (CX), AX
@@ -232,18 +241,22 @@ TEXT ·feSquare(SB), NOSPLIT, $0-16
 	MOVQ DX, BX
 	MOVQ DX, BX
 
 
 	// r0 += 38×l1×l4
 	// 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
 	// 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
 	// r1 = 2×l0×l1
 	MOVQ (CX), AX
 	MOVQ (CX), AX
@@ -253,18 +266,21 @@ TEXT ·feSquare(SB), NOSPLIT, $0-16
 	MOVQ DX, DI
 	MOVQ DX, DI
 
 
 	// r1 += 38×l2×l4
 	// 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
 	// 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
 	// r2 = 2×l0×l2
 	MOVQ (CX), AX
 	MOVQ (CX), AX
@@ -280,11 +296,13 @@ TEXT ·feSquare(SB), NOSPLIT, $0-16
 	ADCQ DX, R9
 	ADCQ DX, R9
 
 
 	// r2 += 38×l3×l4
 	// 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
 	// r3 = 2×l0×l3
 	MOVQ (CX), AX
 	MOVQ (CX), AX
@@ -294,18 +312,19 @@ TEXT ·feSquare(SB), NOSPLIT, $0-16
 	MOVQ DX, R11
 	MOVQ DX, R11
 
 
 	// r3 += 2×l1×l2
 	// 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
 	// 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
 	// r4 = 2×l0×l4
 	MOVQ (CX), AX
 	MOVQ (CX), AX
@@ -315,11 +334,11 @@ TEXT ·feSquare(SB), NOSPLIT, $0-16
 	MOVQ DX, R13
 	MOVQ DX, R13
 
 
 	// r4 += 2×l1×l3
 	// 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
 	// r4 += l2×l2
 	MOVQ 16(CX), AX
 	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
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 // license that can be found in the LICENSE file.
 
 
-//go:build !amd64 || !gc || purego
-// +build !amd64 !gc purego
+//go:build !amd64 || purego
 
 
 package field
 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
 	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)
 	hi, lo := bits.Mul64(a, b)
 	return uint128{lo, hi}
 	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)
 	hi, lo := bits.Mul64(a, b)
 	lo, c := bits.Add64(lo, v.lo, 0)
 	lo, c := bits.Add64(lo, v.lo, 0)
 	hi, _ = bits.Add64(hi, v.hi, c)
 	hi, _ = bits.Add64(hi, v.hi, c)
 	return uint128{lo, hi}
 	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.
 // shiftRightBy51 returns a >> 51. a is assumed to be at most 115 bits.
 func shiftRightBy51(a uint128) uint64 {
 func shiftRightBy51(a uint128) uint64 {
 	return (a.hi << (64 - 51)) | (a.lo >> 51)
 	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.
 	// 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 = 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 = 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 = 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 = 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 = 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
 	// 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⁵¹,
 	// 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)
 	c3 := shiftRightBy51(r3)
 	c4 := shiftRightBy51(r4)
 	c4 := shiftRightBy51(r4)
 
 
-	rr0 := r0.lo&maskLow51Bits + c4*19
+	rr0 := r0.lo&maskLow51Bits + mul19(c4)
 	rr1 := r1.lo&maskLow51Bits + c0
 	rr1 := r1.lo&maskLow51Bits + c0
 	rr2 := r2.lo&maskLow51Bits + c1
 	rr2 := r2.lo&maskLow51Bits + c1
 	rr3 := r3.lo&maskLow51Bits + c2
 	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
 	// 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,
 	// 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⁵¹.
 	// 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) {
 func feSquareGeneric(v, a *Element) {
@@ -190,44 +211,31 @@ func feSquareGeneric(v, a *Element) {
 	//            l0l4 19×l4l4 19×l3l4 19×l2l4 19×l1l4  =
 	//            l0l4 19×l4l4 19×l3l4 19×l2l4 19×l1l4  =
 	//           --------------------------------------
 	//           --------------------------------------
 	//              r4      r3      r2      r1      r0
 	//              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 = 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 = 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 = 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 = 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 = 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)
 	c0 := shiftRightBy51(r0)
 	c1 := shiftRightBy51(r1)
 	c1 := shiftRightBy51(r1)
@@ -235,32 +243,30 @@ func feSquareGeneric(v, a *Element) {
 	c3 := shiftRightBy51(r3)
 	c3 := shiftRightBy51(r3)
 	c4 := shiftRightBy51(r4)
 	c4 := shiftRightBy51(r4)
 
 
-	rr0 := r0.lo&maskLow51Bits + c4*19
+	rr0 := r0.lo&maskLow51Bits + mul19(c4)
 	rr1 := r1.lo&maskLow51Bits + c0
 	rr1 := r1.lo&maskLow51Bits + c0
 	rr2 := r2.lo&maskLow51Bits + c1
 	rr2 := r2.lo&maskLow51Bits + c1
 	rr3 := r3.lo&maskLow51Bits + c2
 	rr3 := r3.lo&maskLow51Bits + c2
 	rr4 := r4.lo&maskLow51Bits + c3
 	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.
 // 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.
 	// 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
 	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 (
 import (
 	"encoding/binary"
 	"encoding/binary"
 	"errors"
 	"errors"
+	"math/bits"
 )
 )
 
 
 // A Scalar is an integer modulo
 // A Scalar is an integer modulo
@@ -179,15 +180,23 @@ func isReduced(s []byte) bool {
 		return false
 		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,
 // SetBytesWithClamping applies the buffer pruning described in RFC 8032,

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

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

+ 2 - 2
vendor/modules.txt

@@ -1,8 +1,8 @@
 # filippo.io/bigmod v0.0.1
 # filippo.io/bigmod v0.0.1
 ## explicit; go 1.20
 ## explicit; go 1.20
 filippo.io/bigmod
 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
 filippo.io/edwards25519/field
 filippo.io/edwards25519/field
 # filippo.io/keygen v0.0.0-20230306160926-5201437acf8e
 # filippo.io/keygen v0.0.0-20230306160926-5201437acf8e